Commit 7181ccbcefd034158f1d95d453c0b63f3090d460

Authored by 芋道源码
Committed by Gitee
2 parents 7b727d5c cfbc8b48

!657 营销活动:完成砍价、秒杀库存回滚。完成拼团过期和虚拟成团处理

Merge pull request !657 from puhui999/feature/mall_product
Showing 32 changed files with 609 additions and 168 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -64,6 +64,13 @@ public class CollectionUtils { @@ -64,6 +64,13 @@ public class CollectionUtils {
64 return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); 64 return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
65 } 65 }
66 66
  67 + public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
  68 + return map.values()
  69 + .stream()
  70 + .flatMap(List::stream)
  71 + .collect(Collectors.toList());
  72 + }
  73 +
67 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) { 74 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
68 if (CollUtil.isEmpty(from)) { 75 if (CollUtil.isEmpty(from)) {
69 return new HashSet<>(); 76 return new HashSet<>();
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
1 package cn.iocoder.yudao.module.promotion.api.combination; 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
5 6
@@ -29,9 +30,9 @@ public interface CombinationRecordApi { @@ -29,9 +30,9 @@ public interface CombinationRecordApi {
29 * 创建开团记录 30 * 创建开团记录
30 * 31 *
31 * @param reqDTO 请求 DTO 32 * @param reqDTO 请求 DTO
32 - * @return 开团记录编号 33 + * @return key 开团记录编号 value 团长编号
33 */ 34 */
34 - Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO); 35 + KeyValue<Long, Long> createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
35 36
36 /** 37 /**
37 * 查询拼团记录是否成功 38 * 查询拼团记录是否成功
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 @@ -10,13 +10,22 @@ import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinResp
10 public interface SeckillActivityApi { 10 public interface SeckillActivityApi {
11 11
12 /** 12 /**
13 - * 更新秒杀库存 13 + * 更新秒杀库存(减少)
14 * 14 *
15 - * @param id 活动编号  
16 - * @param skuId sku 编号  
17 - * @param count 数量 15 + * @param id 活动编号
  16 + * @param skuId sku 编号
  17 + * @param count 数量(正数)
  18 + */
  19 + void updateSeckillStockDecr(Long id, Long skuId, Integer count);
  20 +
  21 + /**
  22 + * 更新秒杀库存(增加)
  23 + *
  24 + * @param id 活动编号
  25 + * @param skuId sku 编号
  26 + * @param count 数量(正数)
18 */ 27 */
19 - void updateSeckillStock(Long id, Long skuId, Integer count); 28 + void updateSeckillStockIncr(Long id, Long skuId, Integer count);
20 29
21 /** 30 /**
22 * 【下单前】校验是否参与秒杀活动 31 * 【下单前】校验是否参与秒杀活动
@@ -24,8 +33,8 @@ public interface SeckillActivityApi { @@ -24,8 +33,8 @@ public interface SeckillActivityApi {
24 * 如果校验失败,则抛出业务异常 33 * 如果校验失败,则抛出业务异常
25 * 34 *
26 * @param activityId 活动编号 35 * @param activityId 活动编号
27 - * @param skuId SKU 编号  
28 - * @param count 数量 36 + * @param skuId SKU 编号
  37 + * @param count 数量
29 * @return 秒杀信息 38 * @return 秒杀信息
30 */ 39 */
31 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); 40 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
1 package cn.iocoder.yudao.module.promotion.api.combination; 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
5 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; 6 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
@@ -29,7 +30,7 @@ public class CombinationRecordApiImpl implements CombinationRecordApi { @@ -29,7 +30,7 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
29 } 30 }
30 31
31 @Override 32 @Override
32 - public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { 33 + public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
33 return recordService.createCombinationRecord(reqDTO); 34 return recordService.createCombinationRecord(reqDTO);
34 } 35 }
35 36
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 { @@ -18,8 +18,13 @@ public class SeckillActivityApiImpl implements SeckillActivityApi {
18 private SeckillActivityService activityService; 18 private SeckillActivityService activityService;
19 19
20 @Override 20 @Override
21 - public void updateSeckillStock(Long id, Long skuId, Integer count) {  
22 - activityService.updateSeckillStock(id, skuId, count); 21 + public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
  22 + activityService.updateSeckillStockDecr(id, skuId, count);
  23 + }
  24 +
  25 + @Override
  26 + public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
  27 + activityService.updateSeckillStockIncr(id, skuId, count);
23 } 28 }
24 29
25 @Override 30 @Override
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; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.combination;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO;
  6 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
6 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO; 8 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO;
8 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; 9 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
@@ -49,15 +50,27 @@ public class CombinationRecordController { @@ -49,15 +50,27 @@ public class CombinationRecordController {
49 return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities)); 50 return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
50 } 51 }
51 52
  53 + @GetMapping("/page-by-headId")
  54 + @Operation(summary = "获得拼团记录分页")
  55 + @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
  56 + public CommonResult<PageResult<CombinationRecordPageItemRespVO>> getBargainRecordPage(@Valid CombinationRecordReqPage2VO pageVO) {
  57 + // 包含团长和团员的分页记录
  58 + PageResult<CombinationRecordDO> recordPage = combinationRecordService.getCombinationRecordPage2(pageVO);
  59 + List<CombinationActivityDO> activities = combinationActivityService.getCombinationActivityListByIds(
  60 + convertSet(recordPage.getList(), CombinationRecordDO::getActivityId));
  61 + return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities));
  62 + }
  63 +
52 @GetMapping("/get-summary") 64 @GetMapping("/get-summary")
53 @Operation(summary = "获得拼团记录的概要信息", description = "用于拼团记录页面展示") 65 @Operation(summary = "获得拼团记录的概要信息", description = "用于拼团记录页面展示")
54 @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')") 66 @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')")
55 public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() { 67 public CommonResult<CombinationRecordSummaryVO> getCombinationRecordSummary() {
56 CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO(); 68 CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO();
57 - summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录 69 + summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null, null)); // 获取拼团用户参与数量
58 summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录 70 summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录
59 - CombinationRecordStatusEnum.SUCCESS.getStatus(), null));  
60 - summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录 71 + CombinationRecordStatusEnum.SUCCESS.getStatus(), null, CombinationRecordDO.HEAD_ID_GROUP));
  72 + summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(// 获取虚拟成团记录
  73 + null, Boolean.TRUE, CombinationRecordDO.HEAD_ID_GROUP));
61 return success(summaryVO); 74 return success(summaryVO);
62 } 75 }
63 76
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +
  11 +@Schema(description = "管理后台 - 拼团记录分页 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class CombinationRecordReqPage2VO extends PageParam {
  16 +
  17 + @Schema(description = "团长编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @NotNull(message = "团长编号不能为空")
  19 + private Long headId;
  20 +
  21 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http 0 → 100644
  1 +### /promotion/activity/list-by-spu-ids 获得多个商品,近期参与的每个活动
  2 +GET {{appApi}}/promotion/activity/list-by-spu-ids?spuIds=222&spuIds=633
  3 +Authorization: Bearer {{appToken}}
  4 +Content-Type: application/json
  5 +tenant-id: {{appTenentId}}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java
@@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation; @@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
27 import io.swagger.v3.oas.annotations.Parameter; 27 import io.swagger.v3.oas.annotations.Parameter;
28 import io.swagger.v3.oas.annotations.Parameters; 28 import io.swagger.v3.oas.annotations.Parameters;
29 import io.swagger.v3.oas.annotations.tags.Tag; 29 import io.swagger.v3.oas.annotations.tags.Tag;
  30 +import org.springframework.context.annotation.Lazy;
30 import org.springframework.validation.annotation.Validated; 31 import org.springframework.validation.annotation.Validated;
31 import org.springframework.web.bind.annotation.*; 32 import org.springframework.web.bind.annotation.*;
32 33
@@ -46,18 +47,23 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti @@ -46,18 +47,23 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
46 public class AppBargainRecordController { 47 public class AppBargainRecordController {
47 48
48 @Resource 49 @Resource
  50 + private BargainHelpService bargainHelpService;
  51 + @Resource
  52 + @Lazy
49 private BargainRecordService bargainRecordService; 53 private BargainRecordService bargainRecordService;
50 @Resource 54 @Resource
  55 + @Lazy
51 private BargainActivityService bargainActivityService; 56 private BargainActivityService bargainActivityService;
52 - @Resource  
53 - private BargainHelpService bargainHelpService; 57 +
54 58
55 @Resource 59 @Resource
  60 + private TradeOrderApi tradeOrderApi;
  61 + @Resource
  62 + @Lazy
56 private MemberUserApi memberUserApi; 63 private MemberUserApi memberUserApi;
57 @Resource 64 @Resource
  65 + @Lazy
58 private ProductSpuApi productSpuApi; 66 private ProductSpuApi productSpuApi;
59 - @Resource  
60 - private TradeOrderApi tradeOrderApi;  
61 67
62 @GetMapping("/get-summary") 68 @GetMapping("/get-summary")
63 @Operation(summary = "获得砍价记录的概要信息", description = "用于小程序首页") 69 @Operation(summary = "获得砍价记录的概要信息", description = "用于小程序首页")
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 { @@ -46,8 +46,8 @@ public class AppCombinationRecordController {
46 @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页") 46 @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页")
47 public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() { 47 public CommonResult<AppCombinationRecordSummaryRespVO> getCombinationRecordSummary() {
48 AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO(); 48 AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO();
49 - // 1. 获得拼团记录数量  
50 - Long count = combinationRecordService.getCombinationRecordCount(null, null); 49 + // 1. 获得拼团参与用户数量
  50 + Long count = combinationRecordService.getCombinationRecordCount(null, null, null);
51 if (count == 0) { 51 if (count == 0) {
52 summary.setAvatars(Collections.emptyList()); 52 summary.setAvatars(Collections.emptyList());
53 summary.setUserCount(count); 53 summary.setUserCount(count);
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; @@ -28,7 +28,7 @@ import org.mapstruct.Mapping;
28 import org.mapstruct.Mappings; 28 import org.mapstruct.Mappings;
29 import org.mapstruct.factory.Mappers; 29 import org.mapstruct.factory.Mappers;
30 30
31 -import java.time.LocalDateTime; 31 +import java.util.ArrayList;
32 import java.util.List; 32 import java.util.List;
33 import java.util.Map; 33 import java.util.Map;
34 34
@@ -114,9 +114,6 @@ public interface CombinationActivityConvert { @@ -114,9 +114,6 @@ public interface CombinationActivityConvert {
114 ProductSpuRespDTO spu, ProductSkuRespDTO sku) { 114 ProductSpuRespDTO spu, ProductSkuRespDTO sku) {
115 return convert(reqDTO).setVirtualGroup(false) 115 return convert(reqDTO).setVirtualGroup(false)
116 .setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中 116 .setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中
117 - .setStartTime(LocalDateTime.now()) // TODO @puhui999:想了下,这个 startTime 应该是团长的;  
118 - // TODO @puhui999:有团长的情况下,expireTime 应该是团长的;  
119 - .setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration()))  
120 .setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录 117 .setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录
121 // 用户信息 118 // 用户信息
122 .setNickname(user.getNickname()).setAvatar(user.getAvatar()) 119 .setNickname(user.getNickname()).setAvatar(user.getAvatar())
@@ -200,4 +197,35 @@ public interface CombinationActivityConvert { @@ -200,4 +197,35 @@ public interface CombinationActivityConvert {
200 return respVO; 197 return respVO;
201 } 198 }
202 199
  200 + /**
  201 + * 转换生成虚拟成团虚拟记录
  202 + *
  203 + * @param virtualGroupHeadRecords 虚拟成团团长记录列表
  204 + * @return 虚拟记录列表
  205 + */
  206 + default List<CombinationRecordDO> convertVirtualGroupList(List<CombinationRecordDO> virtualGroupHeadRecords) {
  207 + List<CombinationRecordDO> createRecords = new ArrayList<>();
  208 + virtualGroupHeadRecords.forEach(headRecord -> {
  209 + // 计算需要创建的虚拟成团记录数量
  210 + int count = headRecord.getUserSize() - headRecord.getUserCount();
  211 + for (int i = 0; i < count; i++) {
  212 + // 基础信息和团长保持一致
  213 + CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(headRecord.getActivityId())
  214 + .setCombinationPrice(headRecord.getCombinationPrice()).setSpuId(headRecord.getSpuId()).setSpuName(headRecord.getSpuName())
  215 + .setPicUrl(headRecord.getPicUrl()).setSkuId(headRecord.getSkuId()).setHeadId(headRecord.getId())
  216 + .setStatus(headRecord.getStatus()) // 状态保持和创建时一致,创建完成后会接着处理
  217 + .setVirtualGroup(headRecord.getVirtualGroup()).setExpireTime(headRecord.getExpireTime())
  218 + .setStartTime(headRecord.getStartTime()).setUserSize(headRecord.getUserSize()).setUserCount(headRecord.getUserCount());
  219 + // 虚拟信息
  220 + newRecord.setCount(0);
  221 + newRecord.setUserId(0L);
  222 + newRecord.setNickname("");
  223 + newRecord.setAvatar("");
  224 + newRecord.setOrderId(0L);
  225 + createRecords.add(newRecord);
  226 + }
  227 + });
  228 + return createRecords;
  229 + }
  230 +
203 } 231 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java
@@ -6,13 +6,15 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -6,13 +6,15 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityPageReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityPageReqVO;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 10 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 import org.apache.ibatis.annotations.Mapper; 11 import org.apache.ibatis.annotations.Mapper;
11 -import org.apache.ibatis.annotations.Select; 12 +import org.apache.ibatis.annotations.Param;
12 13
13 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
14 import java.util.Collection; 15 import java.util.Collection;
15 import java.util.List; 16 import java.util.List;
  17 +import java.util.Map;
16 18
17 /** 19 /**
18 * 砍价活动 Mapper 20 * 砍价活动 Mapper
@@ -85,25 +87,25 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; { @@ -85,25 +87,25 @@ public interface BargainActivityMapper extends BaseMapperX&lt;BargainActivityDO&gt; {
85 .last("LIMIT " + count)); 87 .last("LIMIT " + count));
86 } 88 }
87 89
88 - // TODO @puhui999:一个商品,在统一时间,不会参与多个活动;so 是不是不用 inner join 哈?  
89 - // PS:如果可以参与多个,其实可以这样写 select * from promotion_bargain_activity group by spu_id ORDER BY create_time DESC;通过 group 来过滤  
90 /** 90 /**
91 - * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 91 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
92 * 92 *
93 * @param spuIds spu 编号 93 * @param spuIds spu 编号
94 * @param status 状态 94 * @param status 状态
95 - * @return 砍价活动列表 95 + * @return 包含 spuId 和 activityId 的 map 对象列表
96 */ 96 */
97 - @Select("SELECT p1.* " +  
98 - "FROM promotion_bargain_activity p1 " +  
99 - "INNER JOIN ( " +  
100 - " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +  
101 - " FROM promotion_bargain_activity " +  
102 - " WHERE spu_id IN #{spuIds} " +  
103 - " GROUP BY spu_id " +  
104 - ") p2 " +  
105 - "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +  
106 - "ORDER BY p1.create_time DESC;")  
107 - List<BargainActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status); 97 + default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
  98 + return selectMaps(new QueryWrapper<BargainActivityDO>()
  99 + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
  100 + .in("spu_id", spuIds)
  101 + .eq("status", status)
  102 + .groupBy("spu_id"));
  103 + }
  104 +
  105 + default List<BargainActivityDO> selectListByIds(Collection<Long> ids) {
  106 + return selectList(new LambdaQueryWrapperX<BargainActivityDO>()
  107 + .in(BargainActivityDO::getId, ids)
  108 + .orderByDesc(BargainActivityDO::getCreateTime));
  109 + }
108 110
109 } 111 }
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; @@ -6,12 +6,13 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
9 import org.apache.ibatis.annotations.Mapper; 10 import org.apache.ibatis.annotations.Mapper;
10 import org.apache.ibatis.annotations.Param; 11 import org.apache.ibatis.annotations.Param;
11 -import org.apache.ibatis.annotations.Select;  
12 12
13 import java.util.Collection; 13 import java.util.Collection;
14 import java.util.List; 14 import java.util.List;
  15 +import java.util.Map;
15 16
16 /** 17 /**
17 * 拼团活动 Mapper 18 * 拼团活动 Mapper
@@ -43,24 +44,24 @@ public interface CombinationActivityMapper extends BaseMapperX&lt;CombinationActivi @@ -43,24 +44,24 @@ public interface CombinationActivityMapper extends BaseMapperX&lt;CombinationActivi
43 .last("LIMIT " + count)); 44 .last("LIMIT " + count));
44 } 45 }
45 46
46 - // TODO @puhui999:类似 BargainActivityMapper  
47 /** 47 /**
48 - * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录  
49 - * 48 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
50 * @param spuIds spu 编号 49 * @param spuIds spu 编号
51 * @param status 状态 50 * @param status 状态
52 - * @return 拼团活动列表 51 + * @return 包含 spuId 和 activityId 的 map 对象列表
53 */ 52 */
54 - @Select("SELECT p1.* " +  
55 - "FROM promotion_combination_activity p1 " +  
56 - "INNER JOIN ( " +  
57 - " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +  
58 - " FROM promotion_combination_activity " +  
59 - " WHERE spu_id IN #{spuIds} " +  
60 - " GROUP BY spu_id " +  
61 - ") p2 " +  
62 - "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +  
63 - "ORDER BY p1.create_time DESC;")  
64 - List<CombinationActivityDO> selectListBySpuIds(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status); 53 + default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
  54 + return selectMaps(new QueryWrapper<CombinationActivityDO>()
  55 + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
  56 + .in("spu_id", spuIds)
  57 + .eq("status", status)
  58 + .groupBy("spu_id"));
  59 + }
  60 +
  61 + default List<CombinationActivityDO> selectListByIds(Collection<Long> ids) {
  62 + return selectList(new LambdaQueryWrapperX<CombinationActivityDO>()
  63 + .in(CombinationActivityDO::getId, ids)
  64 + .orderByDesc(CombinationActivityDO::getCreateTime));
  65 + }
65 66
66 } 67 }
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; @@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 7 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
8 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 8 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  9 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
9 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; 10 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
10 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; 11 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
11 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 12 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
12 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
13 14
  15 +import java.time.LocalDateTime;
14 import java.util.Collection; 16 import java.util.Collection;
15 import java.util.Collections; 17 import java.util.Collections;
16 import java.util.List; 18 import java.util.List;
@@ -100,13 +102,40 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO @@ -100,13 +102,40 @@ public interface CombinationRecordMapper extends BaseMapperX&lt;CombinationRecordDO
100 .betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime())); 102 .betweenIfPresent(CombinationRecordDO::getCreateTime, pageVO.getCreateTime()));
101 } 103 }
102 104
103 - // TODO @puhui999:这个最好把 headId 也作为一个参数;因为有个要求 userCount,它要 DISTINCT 下;整体可以参考 selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId  
104 - default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup) {  
105 - return selectCount(new LambdaQueryWrapperX<CombinationRecordDO>()  
106 - .eq(status != null || virtualGroup != null,  
107 - CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长  
108 - .eqIfPresent(CombinationRecordDO::getStatus, status)  
109 - .eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup)); 105 + default PageResult<CombinationRecordDO> selectPage(CombinationRecordReqPage2VO pageVO) {
  106 + return selectPage(pageVO, new LambdaQueryWrapperX<CombinationRecordDO>()
  107 + .eq(CombinationRecordDO::getId, pageVO.getHeadId())
  108 + .or()
  109 + .eq(CombinationRecordDO::getHeadId, pageVO.getHeadId()));
  110 + }
  111 +
  112 + /**
  113 + * 查询指定条件的记录数
  114 + * 如果参数都为 null 时则查询用户拼团记录(DISTINCT 去重),也就是说查询会员表中的用户有多少人参与过拼团活动每个人只统计一次
  115 + *
  116 + * @param status 状态,可为 null
  117 + * @param virtualGroup 是否虚拟成团,可为 null
  118 + * @param headId 团长编号,可为 null
  119 + * @return 记录数
  120 + */
  121 + default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup, Long headId) {
  122 + return selectCount(new QueryWrapper<CombinationRecordDO>()
  123 + .select(status == null && virtualGroup == null && headId == null, "DISTINCT (user_id)")
  124 + .eq(status != null, "status", status)
  125 + .eq(virtualGroup != null, "virtual_group", virtualGroup)
  126 + .eq(headId != null, "head_id", headId)
  127 + .groupBy("user_id"));
  128 + }
  129 +
  130 + default List<CombinationRecordDO> selectListByHeadIdAndStatusAndExpireTimeLt(Long headId, Integer status, LocalDateTime dateTime) {
  131 + return selectList(new LambdaQueryWrapperX<CombinationRecordDO>()
  132 + .eq(CombinationRecordDO::getHeadId, headId)
  133 + .eq(CombinationRecordDO::getStatus, status)
  134 + .lt(CombinationRecordDO::getExpireTime, dateTime));
  135 + }
  136 +
  137 + default List<CombinationRecordDO> selectListByHeadIds(Collection<Long> headIds) {
  138 + return selectList(new LambdaQueryWrapperX<CombinationRecordDO>().in(CombinationRecordDO::getHeadId, headIds));
110 } 139 }
111 140
112 } 141 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
1 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity; 1 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@@ -7,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -7,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; 8 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO;
8 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; 9 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
  11 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
10 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
11 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
12 -import org.apache.ibatis.annotations.Select; 14 +import org.apache.ibatis.annotations.Param;
13 15
14 import java.util.Collection; 16 import java.util.Collection;
15 import java.util.List; 17 import java.util.List;
  18 +import java.util.Map;
16 19
17 /** 20 /**
18 * 秒杀活动 Mapper 21 * 秒杀活动 Mapper
@@ -37,18 +40,32 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; { @@ -37,18 +40,32 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
37 } 40 }
38 41
39 /** 42 /**
40 - * 更新活动库存 43 + * 更新活动库存(减少)
41 * 44 *
42 * @param id 活动编号 45 * @param id 活动编号
43 - * @param count 扣减的库存数量 46 + * @param count 扣减的库存数量(正数)
44 * @return 影响的行数 47 * @return 影响的行数
45 */ 48 */
46 - default int updateStock(Long id, int count) { 49 + default int updateStockDecr(Long id, int count) {
  50 + Assert.isTrue(count > 0);
47 return update(null, new LambdaUpdateWrapper<SeckillActivityDO>() 51 return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
48 .eq(SeckillActivityDO::getId, id) 52 .eq(SeckillActivityDO::getId, id)
49 - .gt(SeckillActivityDO::getTotalStock, 0)  
50 - .setSql("stock = stock + " + count)  
51 - .setSql("total_stock = total_stock - " + count)); 53 + .gt(SeckillActivityDO::getStock, count)
  54 + .setSql("stock = stock - " + count));
  55 + }
  56 +
  57 + /**
  58 + * 更新活动库存(增加)
  59 + *
  60 + * @param id 活动编号
  61 + * @param count 增加的库存数量(正数)
  62 + * @return 影响的行数
  63 + */
  64 + default int updateStockIncr(Long id, int count) {
  65 + Assert.isTrue(count > 0);
  66 + return update(null, new LambdaUpdateWrapper<SeckillActivityDO>()
  67 + .eq(SeckillActivityDO::getId, id)
  68 + .setSql("stock = stock + " + count));
52 } 69 }
53 70
54 default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { 71 default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) {
@@ -58,24 +75,25 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; { @@ -58,24 +75,25 @@ public interface SeckillActivityMapper extends BaseMapperX&lt;SeckillActivityDO&gt; {
58 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); 75 .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0"));
59 } 76 }
60 77
61 - // TODO @puhui999:类似 BargainActivityMapper  
62 /** 78 /**
63 - * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 79 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
64 * 80 *
65 * @param spuIds spu 编号 81 * @param spuIds spu 编号
66 * @param status 状态 82 * @param status 状态
67 - * @return 秒杀活动列表 83 + * @return 包含 spuId 和 activityId 的 map 对象列表
68 */ 84 */
69 - @Select("SELECT p1.* " +  
70 - "FROM promotion_seckill_activity p1 " +  
71 - "INNER JOIN ( " +  
72 - " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " +  
73 - " FROM promotion_seckill_activity " +  
74 - " WHERE spu_id IN #{spuIds} " +  
75 - " GROUP BY spu_id " +  
76 - ") p2 " +  
77 - "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " +  
78 - "ORDER BY p1.create_time DESC;")  
79 - List<SeckillActivityDO> selectListBySpuIds(Collection<Long> spuIds, Integer status); 85 + default List<Map<String, Object>> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection<Long> spuIds, @Param("status") Integer status) {
  86 + return selectMaps(new QueryWrapper<SeckillActivityDO>()
  87 + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id
  88 + .in("spu_id", spuIds)
  89 + .eq("status", status)
  90 + .groupBy("spu_id"));
  91 + }
  92 +
  93 + default List<SeckillActivityDO> selectListByIds(Collection<Long> ids) {
  94 + return selectList(new LambdaQueryWrapperX<SeckillActivityDO>()
  95 + .in(SeckillActivityDO::getId, ids)
  96 + .orderByDesc(SeckillActivityDO::getCreateTime));
  97 + }
80 98
81 } 99 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java
1 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity; 1 package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; 5 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
5 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 6 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -30,17 +31,32 @@ public interface SeckillProductMapper extends BaseMapperX&lt;SeckillProductDO&gt; { @@ -30,17 +31,32 @@ public interface SeckillProductMapper extends BaseMapperX&lt;SeckillProductDO&gt; {
30 } 31 }
31 32
32 /** 33 /**
33 - * 更新活动库存 34 + * 更新活动库存(减少)
34 * 35 *
35 * @param id 活动编号 36 * @param id 活动编号
36 - * @param count 扣减的库存数量 37 + * @param count 扣减的库存数量(减少库存)
37 * @return 影响的行数 38 * @return 影响的行数
38 */ 39 */
39 - default int updateStock(Long id, int count) { 40 + default int updateStockDecr(Long id, int count) {
  41 + Assert.isTrue(count > 0);
40 return update(null, new LambdaUpdateWrapper<SeckillProductDO>() 42 return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
41 .eq(SeckillProductDO::getId, id) 43 .eq(SeckillProductDO::getId, id)
42 .gt(SeckillProductDO::getStock, count) 44 .gt(SeckillProductDO::getStock, count)
43 .setSql("stock = stock - " + count)); 45 .setSql("stock = stock - " + count));
44 } 46 }
45 47
  48 + /**
  49 + * 更新活动库存(增加)
  50 + *
  51 + * @param id 活动编号
  52 + * @param count 需要增加的库存(增加库存)
  53 + * @return 影响的行数
  54 + */
  55 + default int updateStockIncr(Long id, int count) {
  56 + Assert.isTrue(count > 0);
  57 + return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
  58 + .eq(SeckillProductDO::getId, id)
  59 + .setSql("stock = stock + " + count));
  60 + }
  61 +
46 } 62 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.job.combination;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  5 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  6 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  7 +import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import javax.annotation.Resource;
  11 +
  12 +/**
  13 + * 拼团过期 Job
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Component
  18 +public class CombinationRecordExpireJob implements JobHandler {
  19 +
  20 + @Resource
  21 + private CombinationRecordService combinationRecordService;
  22 +
  23 + @Override
  24 + @TenantJob
  25 + public String execute(String param) throws Exception {
  26 + KeyValue<Integer, Integer> keyValue = combinationRecordService.expireCombinationRecord();
  27 + return StrUtil.format("过期拼团 {} 个, 虚拟成团 {} 个", keyValue.getKey(), keyValue.getValue());
  28 + }
  29 +
  30 +}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java
1 package cn.iocoder.yudao.module.promotion.service.bargain; 1 package cn.iocoder.yudao.module.promotion.service.bargain;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.core.util.ObjUtil; 5 import cn.hutool.core.util.ObjUtil;
4 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -20,12 +22,11 @@ import org.springframework.validation.annotation.Validated; @@ -20,12 +22,11 @@ import org.springframework.validation.annotation.Validated;
20 22
21 import javax.annotation.Resource; 23 import javax.annotation.Resource;
22 import java.time.LocalDateTime; 24 import java.time.LocalDateTime;
23 -import java.util.Collection;  
24 -import java.util.List;  
25 -import java.util.Set; 25 +import java.util.*;
26 26
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
29 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; 30 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
30 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 31 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
31 32
@@ -84,11 +85,16 @@ public class BargainActivityServiceImpl implements BargainActivityService { @@ -84,11 +85,16 @@ public class BargainActivityServiceImpl implements BargainActivityService {
84 85
85 @Override 86 @Override
86 public void updateBargainActivityStock(Long id, Integer count) { 87 public void updateBargainActivityStock(Long id, Integer count) {
87 - // 更新库存。如果更新失败,则抛出异常  
88 - int updateCount = bargainActivityMapper.updateStock(id, count);  
89 - if (updateCount == 0) {  
90 - throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH); 88 + if (count < 0) {
  89 + // 更新库存。如果更新失败,则抛出异常
  90 + int updateCount = bargainActivityMapper.updateStock(id, count);
  91 + if (updateCount == 0) {
  92 + throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH);
  93 + }
  94 + } else if (count > 0) {
  95 + bargainActivityMapper.updateStock(id, count);
91 } 96 }
  97 +
92 } 98 }
93 99
94 private void validateBargainConflict(Long spuId, Long activityId) { 100 private void validateBargainConflict(Long spuId, Long activityId) {
@@ -139,7 +145,7 @@ public class BargainActivityServiceImpl implements BargainActivityService { @@ -139,7 +145,7 @@ public class BargainActivityServiceImpl implements BargainActivityService {
139 145
140 @Override 146 @Override
141 public List<BargainActivityDO> getBargainActivityList(Set<Long> ids) { 147 public List<BargainActivityDO> getBargainActivityList(Set<Long> ids) {
142 - return bargainActivityMapper.selectBatchIds(ids); 148 + return bargainActivityMapper.selectBatchIds(ids);
143 } 149 }
144 150
145 @Override 151 @Override
@@ -178,7 +184,13 @@ public class BargainActivityServiceImpl implements BargainActivityService { @@ -178,7 +184,13 @@ public class BargainActivityServiceImpl implements BargainActivityService {
178 184
179 @Override 185 @Override
180 public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { 186 public List<BargainActivityDO> getBargainActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
181 - return bargainActivityMapper.selectListBySpuIds(spuIds, status); 187 + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
  188 + List<Map<String, Object>> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
  189 + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
  190 + return Collections.emptyList();
  191 + }
  192 + // 2.查询活动详情
  193 + return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
182 } 194 }
183 195
184 } 196 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
1 package cn.iocoder.yudao.module.promotion.service.combination; 1 package cn.iocoder.yudao.module.promotion.service.combination;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageParam; 7 import cn.iocoder.yudao.framework.common.pojo.PageParam;
@@ -25,12 +26,12 @@ import org.springframework.validation.annotation.Validated; @@ -25,12 +26,12 @@ import org.springframework.validation.annotation.Validated;
25 26
26 import javax.annotation.Resource; 27 import javax.annotation.Resource;
27 import java.util.Collection; 28 import java.util.Collection;
  29 +import java.util.Collections;
28 import java.util.List; 30 import java.util.List;
29 import java.util.Map; 31 import java.util.Map;
30 32
31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
32 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;  
33 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; 34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
34 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; 35 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
35 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; 36 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
36 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 37 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@@ -228,7 +229,13 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic @@ -228,7 +229,13 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
228 229
229 @Override 230 @Override
230 public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { 231 public List<CombinationActivityDO> getCombinationActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
231 - return combinationActivityMapper.selectListBySpuIds(spuIds, status); 232 + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
  233 + List<Map<String, Object>> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
  234 + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
  235 + return Collections.emptyList();
  236 + }
  237 + // 2.查询活动详情
  238 + return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
232 } 239 }
233 240
234 } 241 }
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; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
6 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; 6 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  7 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
7 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; 8 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO; 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
@@ -49,9 +50,9 @@ public interface CombinationRecordService { @@ -49,9 +50,9 @@ public interface CombinationRecordService {
49 * 创建拼团记录 50 * 创建拼团记录
50 * 51 *
51 * @param reqDTO 创建信息 52 * @param reqDTO 创建信息
52 - * @return 开团记录编号 53 + * @return key 开团记录编号 value 团长编号
53 */ 54 */
54 - Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO); 55 + KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO);
55 56
56 /** 57 /**
57 * 获得拼团记录 58 * 获得拼团记录
@@ -90,9 +91,10 @@ public interface CombinationRecordService { @@ -90,9 +91,10 @@ public interface CombinationRecordService {
90 * 91 *
91 * @param status 状态-允许为空 92 * @param status 状态-允许为空
92 * @param virtualGroup 是否虚拟成团-允许为空 93 * @param virtualGroup 是否虚拟成团-允许为空
  94 + * @param headId 团长编号,允许空。目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 时,可以设置
93 * @return 记录数 95 * @return 记录数
94 */ 96 */
95 - Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup); 97 + Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId);
96 98
97 /** 99 /**
98 * 获取最近的 count 条拼团记录 100 * 获取最近的 count 条拼团记录
@@ -137,6 +139,15 @@ public interface CombinationRecordService { @@ -137,6 +139,15 @@ public interface CombinationRecordService {
137 PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO); 139 PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO);
138 140
139 /** 141 /**
  142 + * 获取拼团记录分页数据(通过团长查询)
  143 + *
  144 + * @param pageVO 分页请求
  145 + * @return 拼团记录分页数据(包括团长的)
  146 + */
  147 + PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO);
  148 +
  149 +
  150 + /**
140 * 【拼团活动】获得拼团记录数量 Map 151 * 【拼团活动】获得拼团记录数量 Map
141 * 152 *
142 * @param activityIds 活动记录编号数组 153 * @param activityIds 活动记录编号数组
@@ -167,5 +178,11 @@ public interface CombinationRecordService { @@ -167,5 +178,11 @@ public interface CombinationRecordService {
167 */ 178 */
168 void cancelCombinationRecord(Long userId, Long id, Long headId); 179 void cancelCombinationRecord(Long userId, Long id, Long headId);
169 180
  181 + /**
  182 + * 处理过期拼团
  183 + *
  184 + * @return key 过期拼团数量, value 虚拟成团数量
  185 + */
  186 + KeyValue<Integer, Integer> expireCombinationRecord();
170 187
171 } 188 }
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; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.combination;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.core.KeyValue; 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
13 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 14 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
14 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; 15 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
15 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; 16 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
  17 +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO;
16 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; 18 import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO;
17 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; 19 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
18 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; 20 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
@@ -22,17 +24,18 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecord @@ -22,17 +24,18 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecord
22 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; 24 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
23 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; 25 import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi;
24 import org.springframework.context.annotation.Lazy; 26 import org.springframework.context.annotation.Lazy;
  27 +import org.springframework.scheduling.annotation.Async;
25 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
26 import org.springframework.transaction.annotation.Transactional; 29 import org.springframework.transaction.annotation.Transactional;
27 import org.springframework.validation.annotation.Validated; 30 import org.springframework.validation.annotation.Validated;
28 31
29 import javax.annotation.Nullable; 32 import javax.annotation.Nullable;
30 import javax.annotation.Resource; 33 import javax.annotation.Resource;
  34 +import java.time.LocalDateTime;
31 import java.util.*; 35 import java.util.*;
32 36
33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 37 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;  
35 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; 38 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
36 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow; 39 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow;
37 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow; 40 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow;
38 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; 41 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@@ -52,7 +55,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -52,7 +55,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
52 @Lazy 55 @Lazy
53 private CombinationActivityService combinationActivityService; 56 private CombinationActivityService combinationActivityService;
54 @Resource 57 @Resource
55 - private CombinationRecordMapper recordMapper; 58 + private CombinationRecordMapper combinationRecordMapper;
56 59
57 @Resource 60 @Resource
58 private MemberUserApi memberUserApi; 61 private MemberUserApi memberUserApi;
@@ -62,6 +65,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -62,6 +65,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
62 @Resource 65 @Resource
63 @Lazy 66 @Lazy
64 private ProductSkuApi productSkuApi; 67 private ProductSkuApi productSkuApi;
  68 +
65 @Resource 69 @Resource
66 private TradeOrderApi tradeOrderApi; 70 private TradeOrderApi tradeOrderApi;
67 71
@@ -74,12 +78,12 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -74,12 +78,12 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
74 78
75 // 更新状态 79 // 更新状态
76 record.setStatus(status); 80 record.setStatus(status);
77 - recordMapper.updateById(record); 81 + combinationRecordMapper.updateById(record);
78 } 82 }
79 83
80 private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) { 84 private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) {
81 // 校验拼团是否存在 85 // 校验拼团是否存在
82 - CombinationRecordDO recordDO = recordMapper.selectByUserIdAndOrderId(userId, orderId); 86 + CombinationRecordDO recordDO = combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
83 if (recordDO == null) { 87 if (recordDO == null) {
84 throw exception(COMBINATION_RECORD_NOT_EXISTS); 88 throw exception(COMBINATION_RECORD_NOT_EXISTS);
85 } 89 }
@@ -108,7 +112,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -108,7 +112,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
108 // 2. 父拼团是否存在,是否已经满了 112 // 2. 父拼团是否存在,是否已经满了
109 if (headId != null) { 113 if (headId != null) {
110 // 2.1. 查询进行中的父拼团 114 // 2.1. 查询进行中的父拼团
111 - CombinationRecordDO record = recordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); 115 + CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus());
112 if (record == null) { 116 if (record == null) {
113 throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS); 117 throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS);
114 } 118 }
@@ -143,7 +147,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -143,7 +147,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
143 } 147 }
144 148
145 // 6.1 校验是否有拼团记录 149 // 6.1 校验是否有拼团记录
146 - List<CombinationRecordDO> recordList = recordMapper.selectListByUserIdAndActivityId(userId, activityId); 150 + List<CombinationRecordDO> recordList = combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
147 recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单,不算数 151 recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单,不算数
148 if (CollUtil.isEmpty(recordList)) { // 如果为空,说明可以参与,直接返回 152 if (CollUtil.isEmpty(recordList)) { // 如果为空,说明可以参与,直接返回
149 return new KeyValue<>(activity, product); 153 return new KeyValue<>(activity, product);
@@ -164,32 +168,36 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -164,32 +168,36 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
164 168
165 @Override 169 @Override
166 @Transactional(rollbackFor = Exception.class) 170 @Transactional(rollbackFor = Exception.class)
167 - public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {  
168 - // 1. 校验拼团活动 171 + public KeyValue<Long, Long> createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) {
  172 + // 1.校验拼团活动
169 KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(), 173 KeyValue<CombinationActivityDO, CombinationProductDO> keyValue = validateCombinationRecord(reqDTO.getUserId(),
170 reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount()); 174 reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount());
171 175
172 - // 2.1 组合数据创建拼团记录 176 + // 2.组合数据创建拼团记录
173 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId()); 177 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId());
174 ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId()); 178 ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId());
175 ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId()); 179 ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId());
176 CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku); 180 CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku);
177 - // 2.2 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP 181 + // 2.1.如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP
178 if (record.getHeadId() == null) { 182 if (record.getHeadId() == null) {
179 - record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); 183 + record.setStartTime(LocalDateTime.now())
  184 + .setExpireTime(keyValue.getKey().getStartTime().plusHours(keyValue.getKey().getLimitDuration()))
  185 + .setHeadId(CombinationRecordDO.HEAD_ID_GROUP);
  186 + } else {
  187 + // 2.2.有团长的情况下需要设置开始时间和过期时间为团长的
  188 + CombinationRecordDO headRecord = combinationRecordMapper.selectByHeadId(record.getHeadId(),
  189 + CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); // 查询进行中的父拼团
  190 + record.setStartTime(headRecord.getStartTime()).setExpireTime(headRecord.getExpireTime());
180 } 191 }
181 - recordMapper.insert(record); 192 + combinationRecordMapper.insert(record);
182 193
183 if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) { 194 if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) {
184 - return record.getId(); 195 + return new KeyValue<>(record.getId(), record.getHeadId());
185 } 196 }
186 197
187 - // TODO @puhui:是不是这里的更新,放到 order 模块那;支付完成后;  
188 - // 4、更新拼团相关信息到订单  
189 - tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId());  
190 - // 4、更新拼团记录 198 + // 3、更新拼团记录
191 updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey()); 199 updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey());
192 - return record.getId(); 200 + return new KeyValue<>(record.getId(), record.getHeadId());
193 } 201 }
194 202
195 /** 203 /**
@@ -204,31 +212,33 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -204,31 +212,33 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
204 if (CollUtil.isEmpty(records)) { 212 if (CollUtil.isEmpty(records)) {
205 return; 213 return;
206 } 214 }
207 - CombinationRecordDO headRecord = recordMapper.selectById(headId); 215 + CombinationRecordDO headRecord = combinationRecordMapper.selectById(headId);
208 216
209 // 2. 批量更新记录 217 // 2. 批量更新记录
210 List<CombinationRecordDO> updateRecords = new ArrayList<>(); 218 List<CombinationRecordDO> updateRecords = new ArrayList<>();
211 records.add(headRecord); // 加入团长,团长也需要更新 219 records.add(headRecord); // 加入团长,团长也需要更新
212 boolean isFull = records.size() >= activity.getUserSize(); 220 boolean isFull = records.size() >= activity.getUserSize();
  221 + LocalDateTime now = LocalDateTime.now();
213 records.forEach(item -> { 222 records.forEach(item -> {
214 CombinationRecordDO updateRecord = new CombinationRecordDO(); 223 CombinationRecordDO updateRecord = new CombinationRecordDO();
215 updateRecord.setId(item.getId()).setUserCount(records.size()); 224 updateRecord.setId(item.getId()).setUserCount(records.size());
216 if (isFull) { 225 if (isFull) {
217 updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus()); 226 updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus());
  227 + updateRecord.setEndTime(now);
218 } 228 }
219 updateRecords.add(updateRecord); 229 updateRecords.add(updateRecord);
220 }); 230 });
221 - recordMapper.updateBatch(updateRecords); 231 + combinationRecordMapper.updateBatch(updateRecords);
222 } 232 }
223 233
224 @Override 234 @Override
225 public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) { 235 public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) {
226 - return recordMapper.selectByUserIdAndOrderId(userId, orderId); 236 + return combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId);
227 } 237 }
228 238
229 @Override 239 @Override
230 public List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) { 240 public List<CombinationRecordDO> getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) {
231 - return recordMapper.selectListByUserIdAndActivityId(userId, activityId); 241 + return combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId);
232 } 242 }
233 243
234 @Override 244 @Override
@@ -241,52 +251,57 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -241,52 +251,57 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
241 } 251 }
242 252
243 @Override 253 @Override
244 - public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) {  
245 - return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup); 254 + public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId) {
  255 + return combinationRecordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup, headId);
246 } 256 }
247 257
248 @Override 258 @Override
249 public List<CombinationRecordDO> getLatestCombinationRecordList(int count) { 259 public List<CombinationRecordDO> getLatestCombinationRecordList(int count) {
250 - return recordMapper.selectLatestList(count); 260 + return combinationRecordMapper.selectLatestList(count);
251 } 261 }
252 262
253 @Override 263 @Override
254 public List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count) { 264 public List<CombinationRecordDO> getHeadCombinationRecordList(Long activityId, Integer status, Integer count) {
255 - return recordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status, 265 + return combinationRecordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status,
256 CombinationRecordDO.HEAD_ID_GROUP, count); 266 CombinationRecordDO.HEAD_ID_GROUP, count);
257 } 267 }
258 268
259 @Override 269 @Override
260 public CombinationRecordDO getCombinationRecordById(Long id) { 270 public CombinationRecordDO getCombinationRecordById(Long id) {
261 - return recordMapper.selectById(id); 271 + return combinationRecordMapper.selectById(id);
262 } 272 }
263 273
264 @Override 274 @Override
265 public List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId) { 275 public List<CombinationRecordDO> getCombinationRecordListByHeadId(Long headId) {
266 - return recordMapper.selectList(CombinationRecordDO::getHeadId, headId); 276 + return combinationRecordMapper.selectList(CombinationRecordDO::getHeadId, headId);
267 } 277 }
268 278
269 @Override 279 @Override
270 public PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO) { 280 public PageResult<CombinationRecordDO> getCombinationRecordPage(CombinationRecordReqPageVO pageVO) {
271 - return recordMapper.selectPage(pageVO); 281 + return combinationRecordMapper.selectPage(pageVO);
  282 + }
  283 +
  284 + @Override
  285 + public PageResult<CombinationRecordDO> getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO) {
  286 + return combinationRecordMapper.selectPage(pageVO);
272 } 287 }
273 288
274 @Override 289 @Override
275 public Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds, 290 public Map<Long, Integer> getCombinationRecordCountMapByActivity(Collection<Long> activityIds,
276 @Nullable Integer status, @Nullable Long headId) { 291 @Nullable Integer status, @Nullable Long headId) {
277 - return recordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId); 292 + return combinationRecordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId);
278 } 293 }
279 294
280 @Override 295 @Override
281 public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) { 296 public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) {
282 - return recordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id); 297 + return combinationRecordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id);
283 } 298 }
284 299
285 @Override 300 @Override
286 @Transactional(rollbackFor = Exception.class) 301 @Transactional(rollbackFor = Exception.class)
287 public void cancelCombinationRecord(Long userId, Long id, Long headId) { 302 public void cancelCombinationRecord(Long userId, Long id, Long headId) {
288 // 删除记录 303 // 删除记录
289 - recordMapper.deleteById(id); 304 + combinationRecordMapper.deleteById(id);
290 305
291 // 需要更新的记录 306 // 需要更新的记录
292 List<CombinationRecordDO> updateRecords = new ArrayList<>(); 307 List<CombinationRecordDO> updateRecords = new ArrayList<>();
@@ -313,7 +328,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -313,7 +328,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
313 }); 328 });
314 } else { // 情况二:团员 329 } else { // 情况二:团员
315 // 团长 330 // 团长
316 - CombinationRecordDO recordHead = recordMapper.selectById(headId); 331 + CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId);
317 // 团员 332 // 团员
318 List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId); 333 List<CombinationRecordDO> records = getCombinationRecordListByHeadId(headId);
319 if (CollUtil.isEmpty(records)) { 334 if (CollUtil.isEmpty(records)) {
@@ -329,7 +344,112 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @@ -329,7 +344,112 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
329 } 344 }
330 345
331 // 更新拼团记录 346 // 更新拼团记录
332 - recordMapper.updateBatch(updateRecords); 347 + combinationRecordMapper.updateBatch(updateRecords);
  348 + }
  349 +
  350 + @Override
  351 + public KeyValue<Integer, Integer> expireCombinationRecord() {
  352 + // 1。获取所有正在进行中的过期的父拼团
  353 + List<CombinationRecordDO> headExpireRecords = combinationRecordMapper.selectListByHeadIdAndStatusAndExpireTimeLt(
  354 + CombinationRecordDO.HEAD_ID_GROUP, CombinationRecordStatusEnum.IN_PROGRESS.getStatus(), LocalDateTime.now());
  355 + if (CollUtil.isEmpty(headExpireRecords)) {
  356 + return new KeyValue<>(0, 0);
  357 + }
  358 +
  359 + // 2.获取拼团活动
  360 + List<CombinationActivityDO> combinationActivities = combinationActivityService.getCombinationActivityListByIds(
  361 + convertSet(headExpireRecords, CombinationRecordDO::getActivityId));
  362 + Map<Long, CombinationActivityDO> activityMap = convertMap(combinationActivities, CombinationActivityDO::getId);
  363 +
  364 + // 3.校验是否虚拟成团
  365 + List<CombinationRecordDO> virtualGroupHeadRecords = new ArrayList<>(); // 虚拟成团
  366 + for (Iterator<CombinationRecordDO> iterator = headExpireRecords.iterator(); iterator.hasNext(); ) {
  367 + CombinationRecordDO record = iterator.next();
  368 + // 3.1 不匹配,则直接跳过
  369 + CombinationActivityDO activityDO = activityMap.get(record.getActivityId());
  370 + if (activityDO == null || !activityDO.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的
  371 + continue;
  372 + }
  373 + // 3.2 匹配,则移除,添加到虚拟成团中,并结束寻找
  374 + virtualGroupHeadRecords.add(record);
  375 + iterator.remove();
  376 + break;
  377 + }
  378 +
  379 + // 4.处理过期的拼团
  380 + getSelf().handleExpireRecord(headExpireRecords);
  381 + // 5.虚拟成团
  382 + getSelf().handleVirtualGroupRecord(virtualGroupHeadRecords);
  383 +
  384 + return new KeyValue<>(headExpireRecords.size(), virtualGroupHeadRecords.size());
  385 + }
  386 +
  387 + @Async
  388 + protected void handleExpireRecord(List<CombinationRecordDO> headExpireRecords) {
  389 + if (CollUtil.isEmpty(headExpireRecords)) {
  390 + return;
  391 + }
  392 +
  393 + // 1.更新拼团记录
  394 + List<CombinationRecordDO> headsAndRecords = updateBatchCombinationRecords(headExpireRecords,
  395 + CombinationRecordStatusEnum.FAILED);
  396 + if (headsAndRecords == null) {
  397 + return;
  398 + }
  399 +
  400 + // 2.订单取消 TODO 以现在的取消回滚逻辑好像只能循环了
  401 + headsAndRecords.forEach(item -> {
  402 + tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId());
  403 + });
  404 +
  405 + }
  406 +
  407 + @Async
  408 + protected void handleVirtualGroupRecord(List<CombinationRecordDO> virtualGroupHeadRecords) {
  409 + if (CollUtil.isEmpty(virtualGroupHeadRecords)) {
  410 + return;
  411 + }
  412 +
  413 + // 1.团员补齐
  414 + combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecords));
  415 + // 2.更新拼团记录
  416 + updateBatchCombinationRecords(virtualGroupHeadRecords, CombinationRecordStatusEnum.SUCCESS);
  417 + }
  418 +
  419 + private List<CombinationRecordDO> updateBatchCombinationRecords(List<CombinationRecordDO> headRecords, CombinationRecordStatusEnum status) {
  420 + // 1. 查询团成员
  421 + List<CombinationRecordDO> records = combinationRecordMapper.selectListByHeadIds(
  422 + convertSet(headRecords, CombinationRecordDO::getId));
  423 + if (CollUtil.isEmpty(records)) {
  424 + return null;
  425 + }
  426 + Map<Long, List<CombinationRecordDO>> recordsMap = convertMultiMap(records, CombinationRecordDO::getHeadId);
  427 + headRecords.forEach(item -> {
  428 + recordsMap.get(item.getId()).add(item); // 把团长加进团里
  429 + });
  430 + // 2.批量更新拼团记录 status 和 失败/成团时间
  431 + List<CombinationRecordDO> headsAndRecords = mergeValuesFromMap(recordsMap);
  432 + List<CombinationRecordDO> updateRecords = new ArrayList<>(headsAndRecords.size());
  433 + LocalDateTime now = LocalDateTime.now();
  434 + headsAndRecords.forEach(item -> {
  435 + CombinationRecordDO record = new CombinationRecordDO().setId(item.getId())
  436 + .setStatus(status.getStatus()).setEndTime(now);
  437 + if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
  438 + record.setUserCount(record.getUserSize());
  439 + }
  440 + updateRecords.add(record);
  441 + });
  442 + combinationRecordMapper.updateBatch(updateRecords);
  443 + return headsAndRecords;
  444 + }
  445 +
  446 + /**
  447 + * 获得自身的代理对象,解决 AOP 生效问题
  448 + *
  449 + * @return 自己
  450 + */
  451 + private CombinationRecordServiceImpl getSelf() {
  452 + return SpringUtil.getBean(getClass());
333 } 453 }
334 454
335 } 455 }
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 { @@ -36,13 +36,22 @@ public interface SeckillActivityService {
36 void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO); 36 void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO);
37 37
38 /** 38 /**
39 - * 更新秒杀库存 39 + * 更新秒杀库存(减少)
40 * 40 *
41 * @param id 活动编号 41 * @param id 活动编号
42 * @param skuId sku 编号 42 * @param skuId sku 编号
43 - * @param count 数量 43 + * @param count 数量(正数)
44 */ 44 */
45 - void updateSeckillStock(Long id, Long skuId, Integer count); 45 + void updateSeckillStockDecr(Long id, Long skuId, Integer count);
  46 +
  47 + /**
  48 + * 更新秒杀库存(增加)
  49 + *
  50 + * @param id 活动编号
  51 + * @param skuId sku 编号
  52 + * @param count 数量(正数)
  53 + */
  54 + void updateSeckillStockIncr(Long id, Long skuId, Integer count);
46 55
47 /** 56 /**
48 * 关闭秒杀活动 57 * 关闭秒杀活动
@@ -113,8 +122,8 @@ public interface SeckillActivityService { @@ -113,8 +122,8 @@ public interface SeckillActivityService {
113 * 如果校验失败,则抛出业务异常 122 * 如果校验失败,则抛出业务异常
114 * 123 *
115 * @param activityId 活动编号 124 * @param activityId 活动编号
116 - * @param skuId SKU 编号  
117 - * @param count 数量 125 + * @param skuId SKU 编号
  126 + * @param count 数量
118 * @return 秒杀信息 127 * @return 秒杀信息
119 */ 128 */
120 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); 129 SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count);
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
1 package cn.iocoder.yudao.module.promotion.service.seckill; 1 package cn.iocoder.yudao.module.promotion.service.seckill;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.collection.CollectionUtil; 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.ObjectUtil; 6 import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -27,6 +29,7 @@ import org.springframework.validation.annotation.Validated; @@ -27,6 +29,7 @@ import org.springframework.validation.annotation.Validated;
27 29
28 import javax.annotation.Resource; 30 import javax.annotation.Resource;
29 import java.util.Collection; 31 import java.util.Collection;
  32 +import java.util.Collections;
30 import java.util.List; 33 import java.util.List;
31 import java.util.Map; 34 import java.util.Map;
32 35
@@ -154,7 +157,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -154,7 +157,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
154 157
155 @Override 158 @Override
156 @Transactional(rollbackFor = Exception.class) 159 @Transactional(rollbackFor = Exception.class)
157 - public void updateSeckillStock(Long id, Long skuId, Integer count) { 160 + public void updateSeckillStockDecr(Long id, Long skuId, Integer count) {
158 // 1.1 校验活动库存是否充足 161 // 1.1 校验活动库存是否充足
159 SeckillActivityDO seckillActivity = validateSeckillActivityExists(id); 162 SeckillActivityDO seckillActivity = validateSeckillActivityExists(id);
160 if (count > seckillActivity.getTotalStock()) { 163 if (count > seckillActivity.getTotalStock()) {
@@ -167,18 +170,28 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -167,18 +170,28 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
167 } 170 }
168 171
169 // 2.1 更新活动商品库存 172 // 2.1 更新活动商品库存
170 - int updateCount = seckillProductMapper.updateStock(product.getId(), count); 173 + int updateCount = seckillProductMapper.updateStockDecr(product.getId(), count);
171 if (updateCount == 0) { 174 if (updateCount == 0) {
172 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); 175 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
173 } 176 }
174 177
175 // 2.2 更新活动库存 178 // 2.2 更新活动库存
176 - updateCount = seckillActivityMapper.updateStock(seckillActivity.getId(), count); 179 + updateCount = seckillActivityMapper.updateStockDecr(seckillActivity.getId(), count);
177 if (updateCount == 0) { 180 if (updateCount == 0) {
178 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); 181 throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL);
179 } 182 }
180 } 183 }
181 184
  185 + @Override
  186 + @Transactional(rollbackFor = Exception.class)
  187 + public void updateSeckillStockIncr(Long id, Long skuId, Integer count) {
  188 + SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(id, skuId);
  189 + // 更新活动商品库存
  190 + seckillProductMapper.updateStockIncr(product.getId(), count);
  191 + // 更新活动库存
  192 + seckillActivityMapper.updateStockIncr(id, count);
  193 + }
  194 +
182 /** 195 /**
183 * 更新秒杀商品 196 * 更新秒杀商品
184 * 197 *
@@ -312,7 +325,13 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @@ -312,7 +325,13 @@ public class SeckillActivityServiceImpl implements SeckillActivityService {
312 325
313 @Override 326 @Override
314 public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) { 327 public List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatus(Collection<Long> spuIds, Integer status) {
315 - return seckillActivityMapper.selectListBySpuIds(spuIds, status); 328 + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号
  329 + List<Map<String, Object>> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status);
  330 + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) {
  331 + return Collections.emptyList();
  332 + }
  333 + // 2.查询活动详情
  334 + return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId")));
316 } 335 }
317 336
318 } 337 }
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 { @@ -33,7 +33,8 @@ public interface ErrorCodeConstants {
33 ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元"); 33 ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元");
34 ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态"); 34 ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态");
35 ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】"); 35 ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】");
36 - ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货"); 36 + ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
  37 + ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
37 38
38 // ========== After Sale 模块 1-011-000-100 ========== 39 // ========== After Sale 模块 1-011-000-100 ==========
39 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在"); 40 ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO; @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderSummaryRespDTO;
5 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; 5 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
6 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; 6 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
7 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; 7 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
  8 +import org.springframework.context.annotation.Lazy;
8 import org.springframework.stereotype.Service; 9 import org.springframework.stereotype.Service;
9 import org.springframework.validation.annotation.Validated; 10 import org.springframework.validation.annotation.Validated;
10 11
@@ -23,9 +24,10 @@ import java.util.List; @@ -23,9 +24,10 @@ import java.util.List;
23 public class TradeOrderApiImpl implements TradeOrderApi { 24 public class TradeOrderApiImpl implements TradeOrderApi {
24 25
25 @Resource 26 @Resource
26 - private TradeOrderQueryService tradeOrderQueryService;  
27 - @Resource  
28 private TradeOrderUpdateService tradeOrderUpdateService; 27 private TradeOrderUpdateService tradeOrderUpdateService;
  28 + @Resource
  29 + @Lazy
  30 + private TradeOrderQueryService tradeOrderQueryService;
29 31
30 @Override 32 @Override
31 public List<TradeOrderRespDTO> getOrderList(Collection<Long> ids) { 33 public List<TradeOrderRespDTO> getOrderList(Collection<Long> ids) {
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
@@ -110,4 +110,16 @@ public interface TradeOrderMapper extends BaseMapperX&lt;TradeOrderDO&gt; { @@ -110,4 +110,16 @@ public interface TradeOrderMapper extends BaseMapperX&lt;TradeOrderDO&gt; {
110 return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode); 110 return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode);
111 } 111 }
112 112
  113 + default TradeOrderDO selectByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
  114 + return selectOne(new LambdaQueryWrapperX<TradeOrderDO>()
  115 + .and(q -> q.eq(TradeOrderDO::getUserId, userId)
  116 + .eq(TradeOrderDO::getStatus, status))
  117 + .and(q -> q.eq(TradeOrderDO::getCombinationActivityId, activityId)
  118 + .or()
  119 + .eq(TradeOrderDO::getSeckillActivityId, activityId)
  120 + .or()
  121 + .eq(TradeOrderDO::getBargainActivityId, activityId))
  122 + );
  123 + }
  124 +
113 } 125 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
@@ -41,6 +41,16 @@ public interface TradeOrderQueryService { @@ -41,6 +41,16 @@ public interface TradeOrderQueryService {
41 TradeOrderDO getOrder(Long userId, Long id); 41 TradeOrderDO getOrder(Long userId, Long id);
42 42
43 /** 43 /**
  44 + * 获得指定用户,指定活动,指定状态的交易订单
  45 + *
  46 + * @param userId 用户编号
  47 + * @param activityId 活动编号
  48 + * @param status 订单状态
  49 + * @return 交易订单
  50 + */
  51 + TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status);
  52 +
  53 + /**
44 * 获得订单列表 54 * 获得订单列表
45 * 55 *
46 * @param ids 订单编号数组 56 * @param ids 订单编号数组
@@ -95,7 +105,7 @@ public interface TradeOrderQueryService { @@ -95,7 +105,7 @@ public interface TradeOrderQueryService {
95 /** 105 /**
96 * 【会员】在指定秒杀活动下,用户购买的商品数量 106 * 【会员】在指定秒杀活动下,用户购买的商品数量
97 * 107 *
98 - * @param userId 用户编号 108 + * @param userId 用户编号
99 * @param activityId 活动编号 109 * @param activityId 活动编号
100 * @return 秒杀商品数量 110 * @return 秒杀商品数量
101 */ 111 */
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
@@ -73,6 +73,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { @@ -73,6 +73,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService {
73 } 73 }
74 74
75 @Override 75 @Override
  76 + public TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) {
  77 + return tradeOrderMapper.selectByUserIdAndActivityIdAndStatus(userId, activityId, status);
  78 + }
  79 +
  80 + @Override
76 public List<TradeOrderDO> getOrderList(Collection<Long> ids) { 81 public List<TradeOrderDO> getOrderList(Collection<Long> ids) {
77 if (CollUtil.isEmpty(ids)) { 82 if (CollUtil.isEmpty(ids)) {
78 return Collections.emptyList(); 83 return Collections.emptyList();
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -742,9 +742,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -742,9 +742,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
742 public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) { 742 public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) {
743 // 校验交易订单 743 // 校验交易订单
744 TradeOrderDO order = validateOrderExists(reqVO.getId()); 744 TradeOrderDO order = validateOrderExists(reqVO.getId());
745 - // 发货后,不允许修改;  
746 - // TODO @puhui999:只有待发货,可以执行 update  
747 - if (TradeOrderStatusEnum.isDelivered(order.getStatus())) { 745 + // 只有待发货状态才可以修改订单收货地址;
  746 + if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) {
748 throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED); 747 throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED);
749 } 748 }
750 749
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java
1 package cn.iocoder.yudao.module.trade.service.order.handler; 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi; 4 import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
4 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi; 5 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 6 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -28,6 +29,8 @@ public class TradeBargainHandler implements TradeOrderHandler { @@ -28,6 +29,8 @@ public class TradeBargainHandler implements TradeOrderHandler {
28 if (TradeOrderTypeEnum.isBargain(order.getType())) { 29 if (TradeOrderTypeEnum.isBargain(order.getType())) {
29 return; 30 return;
30 } 31 }
  32 + // 明确校验一下
  33 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
31 34
32 // 扣减砍价活动的库存 35 // 扣减砍价活动的库存
33 bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), 36 bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
@@ -39,6 +42,8 @@ public class TradeBargainHandler implements TradeOrderHandler { @@ -39,6 +42,8 @@ public class TradeBargainHandler implements TradeOrderHandler {
39 if (TradeOrderTypeEnum.isBargain(order.getType())) { 42 if (TradeOrderTypeEnum.isBargain(order.getType())) {
40 return; 43 return;
41 } 44 }
  45 + // 明确校验一下
  46 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
42 47
43 // 记录砍价记录对应的订单编号 48 // 记录砍价记录对应的订单编号
44 bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId()); 49 bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId());
@@ -49,7 +54,12 @@ public class TradeBargainHandler implements TradeOrderHandler { @@ -49,7 +54,12 @@ public class TradeBargainHandler implements TradeOrderHandler {
49 if (TradeOrderTypeEnum.isBargain(order.getType())) { 54 if (TradeOrderTypeEnum.isBargain(order.getType())) {
50 return; 55 return;
51 } 56 }
52 - // TODO 芋艿:取消订单时,需要增加库存 57 + // 明确校验一下
  58 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
  59 +
  60 + // 恢复砍价活动的库存
  61 + bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
  62 + orderItems.get(0).getCount());
53 } 63 }
54 64
55 } 65 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java
1 package cn.iocoder.yudao.module.trade.service.order.handler; 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; 5 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
5 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; 6 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
6 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 7 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
7 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 8 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
  9 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
8 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 10 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
  11 +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
  12 +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
  13 +import org.springframework.context.annotation.Lazy;
9 import org.springframework.stereotype.Component; 14 import org.springframework.stereotype.Component;
10 15
11 import javax.annotation.Resource; 16 import javax.annotation.Resource;
12 import java.util.List; 17 import java.util.List;
13 18
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID;
  21 +
14 /** 22 /**
15 * 拼团订单 handler 接口实现类 23 * 拼团订单 handler 接口实现类
16 * 24 *
@@ -20,6 +28,14 @@ import java.util.List; @@ -20,6 +28,14 @@ import java.util.List;
20 public class TradeCombinationHandler implements TradeOrderHandler { 28 public class TradeCombinationHandler implements TradeOrderHandler {
21 29
22 @Resource 30 @Resource
  31 + @Lazy
  32 + private TradeOrderUpdateService orderUpdateService;
  33 + @Resource
  34 + @Lazy
  35 + private TradeOrderQueryService orderQueryService;
  36 +
  37 + @Resource
  38 + @Lazy
23 private CombinationRecordApi combinationRecordApi; 39 private CombinationRecordApi combinationRecordApi;
24 40
25 @Override 41 @Override
@@ -34,28 +50,30 @@ public class TradeCombinationHandler implements TradeOrderHandler { @@ -34,28 +50,30 @@ public class TradeCombinationHandler implements TradeOrderHandler {
34 TradeOrderItemDO item = orderItems.get(0); 50 TradeOrderItemDO item = orderItems.get(0);
35 combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(), 51 combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
36 order.getCombinationHeadId(), item.getSkuId(), item.getCount()); 52 order.getCombinationHeadId(), item.getSkuId(), item.getCount());
37 - // TODO @puhui999:这里还要限制下,是不是已经 createOrder;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了; 53 + // 校验该用户是否存在未支付的拼团活动订单;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了;
  54 + TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus(
  55 + order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus());
  56 + if (activityOrder != null) {
  57 + throw exception(ORDER_CREATE_FAIL_EXIST_UNPAID);
  58 + }
38 } 59 }
39 60
40 @Override 61 @Override
41 public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) { 62 public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
42 - // 如果不是拼团订单则结束 63 + // 1.如果不是拼团订单则结束
43 if (TradeOrderTypeEnum.isCombination(order.getType())) { 64 if (TradeOrderTypeEnum.isCombination(order.getType())) {
44 return; 65 return;
45 } 66 }
46 Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品"); 67 Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
47 68
48 - // 获取商品信息 69 + // 2.获取商品信息
49 TradeOrderItemDO item = orderItems.get(0); 70 TradeOrderItemDO item = orderItems.get(0);
50 - // 创建拼团记录  
51 - combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item));  
52 - }  
53 -  
54 - @Override  
55 - public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {  
56 - if (TradeOrderTypeEnum.isCombination(order.getType())) {  
57 - return;  
58 - } 71 + // 2.1.创建拼团记录
  72 + KeyValue<Long, Long> recordIdAndHeadId = combinationRecordApi.createCombinationRecord(
  73 + TradeOrderConvert.INSTANCE.convert(order, item));
  74 + // 3.更新拼团相关信息到订单
  75 + orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(),
  76 + recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue());
59 } 77 }
60 78
61 } 79 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java
1 package cn.iocoder.yudao.module.trade.service.order.handler; 1 package cn.iocoder.yudao.module.trade.service.order.handler;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; 4 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
4 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
5 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; 6 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
@@ -25,9 +26,11 @@ public class TradeSeckillHandler implements TradeOrderHandler { @@ -25,9 +26,11 @@ public class TradeSeckillHandler implements TradeOrderHandler {
25 if (TradeOrderTypeEnum.isSeckill(order.getType())) { 26 if (TradeOrderTypeEnum.isSeckill(order.getType())) {
26 return; 27 return;
27 } 28 }
  29 + // 明确校验一下
  30 + Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
28 31
29 // 扣减秒杀活动的库存 32 // 扣减秒杀活动的库存
30 - seckillActivityApi.updateSeckillStock(order.getSeckillActivityId(), 33 + seckillActivityApi.updateSeckillStockDecr(order.getSeckillActivityId(),
31 orderItems.get(0).getSkuId(), orderItems.get(0).getCount()); 34 orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
32 } 35 }
33 36
@@ -36,7 +39,12 @@ public class TradeSeckillHandler implements TradeOrderHandler { @@ -36,7 +39,12 @@ public class TradeSeckillHandler implements TradeOrderHandler {
36 if (TradeOrderTypeEnum.isSeckill(order.getType())) { 39 if (TradeOrderTypeEnum.isSeckill(order.getType())) {
37 return; 40 return;
38 } 41 }
  42 + // 明确校验一下
  43 + Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
39 44
  45 + // 恢复秒杀活动的库存
  46 + seckillActivityApi.updateSeckillStockIncr(order.getSeckillActivityId(),
  47 + orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
40 } 48 }
41 49
42 } 50 }