diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index e63dd5aee6..394651a980 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -72,6 +72,13 @@ public class CollectionUtils { return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); } + public static List mergeValuesFromMap(Map> map) { + return map.values() + .stream() + .flatMap(List::stream) + .collect(Collectors.toList()); + } + public static Set convertSet(Collection from, Function func) { if (CollUtil.isEmpty(from)) { return new HashSet<>(); diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java index 0bb7674237..f45b0be1f0 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.api.combination; +import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; @@ -29,9 +30,9 @@ public interface CombinationRecordApi { * 创建开团记录 * * @param reqDTO 请求 DTO - * @return 开团记录编号 + * @return key 开团记录编号 value 团长编号 */ - Long createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO); + KeyValue createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO); /** * 查询拼团记录是否成功 diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java index 11b75e03c2..0d65919d15 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java @@ -10,13 +10,22 @@ import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinResp public interface SeckillActivityApi { /** - * 更新秒杀库存 + * 更新秒杀库存(减少) * - * @param id 活动编号 - * @param skuId sku 编号 - * @param count 数量 + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) */ - void updateSeckillStock(Long id, Long skuId, Integer count); + void updateSeckillStockDecr(Long id, Long skuId, Integer count); + + /** + * 更新秒杀库存(增加) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updateSeckillStockIncr(Long id, Long skuId, Integer count); /** * 【下单前】校验是否参与秒杀活动 @@ -24,8 +33,8 @@ public interface SeckillActivityApi { * 如果校验失败,则抛出业务异常 * * @param activityId 活动编号 - * @param skuId SKU 编号 - * @param count 数量 + * @param skuId SKU 编号 + * @param count 数量 * @return 秒杀信息 */ SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java index d4acba1c37..4a3f1dbd54 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.api.combination; +import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; @@ -29,7 +30,7 @@ public class CombinationRecordApiImpl implements CombinationRecordApi { } @Override - public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { + public KeyValue createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { return recordService.createCombinationRecord(reqDTO); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java index 239cdf4ba3..45e2d46985 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java @@ -18,8 +18,13 @@ public class SeckillActivityApiImpl implements SeckillActivityApi { private SeckillActivityService activityService; @Override - public void updateSeckillStock(Long id, Long skuId, Integer count) { - activityService.updateSeckillStock(id, skuId, count); + public void updateSeckillStockDecr(Long id, Long skuId, Integer count) { + activityService.updateSeckillStockDecr(id, skuId, count); + } + + @Override + public void updateSeckillStockIncr(Long id, Long skuId, Integer count) { + activityService.updateSeckillStockIncr(id, skuId, count); } @Override diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java index 88a026fe22..98a4f3cdd4 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationRecordController.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.combination; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordPageItemRespVO; +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordSummaryVO; import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; @@ -49,15 +50,27 @@ public class CombinationRecordController { return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities)); } + @GetMapping("/page-by-headId") + @Operation(summary = "获得拼团记录分页") + @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')") + public CommonResult> getBargainRecordPage(@Valid CombinationRecordReqPage2VO pageVO) { + // 包含团长和团员的分页记录 + PageResult recordPage = combinationRecordService.getCombinationRecordPage2(pageVO); + List activities = combinationActivityService.getCombinationActivityListByIds( + convertSet(recordPage.getList(), CombinationRecordDO::getActivityId)); + return success(CombinationActivityConvert.INSTANCE.convert(recordPage, activities)); + } + @GetMapping("/get-summary") @Operation(summary = "获得拼团记录的概要信息", description = "用于拼团记录页面展示") @PreAuthorize("@ss.hasPermission('promotion:combination-record:query')") public CommonResult getCombinationRecordSummary() { CombinationRecordSummaryVO summaryVO = new CombinationRecordSummaryVO(); - summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null)); // 获取所有拼团记录 + summaryVO.setUserCount(combinationRecordService.getCombinationRecordCount(null, null, null)); // 获取拼团用户参与数量 summaryVO.setSuccessCount(combinationRecordService.getCombinationRecordCount( // 获取成团记录 - CombinationRecordStatusEnum.SUCCESS.getStatus(), null)); - summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(null, Boolean.TRUE));// 获取虚拟成团记录 + CombinationRecordStatusEnum.SUCCESS.getStatus(), null, CombinationRecordDO.HEAD_ID_GROUP)); + summaryVO.setVirtualGroupCount(combinationRecordService.getCombinationRecordCount(// 获取虚拟成团记录 + null, Boolean.TRUE, CombinationRecordDO.HEAD_ID_GROUP)); return success(summaryVO); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java new file mode 100644 index 0000000000..9e6fe9159f --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/recrod/CombinationRecordReqPage2VO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 拼团记录分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CombinationRecordReqPage2VO extends PageParam { + + @Schema(description = "团长编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "团长编号不能为空") + private Long headId; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http new file mode 100644 index 0000000000..0dda88c7d8 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/activity/AppActivityController.http @@ -0,0 +1,5 @@ +### /promotion/activity/list-by-spu-ids 获得多个商品,近期参与的每个活动 +GET {{appApi}}/promotion/activity/list-by-spu-ids?spuIds=222&spuIds=633 +Authorization: Bearer {{appToken}} +Content-Type: application/json +tenant-id: {{appTenentId}} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java index d371633562..e09e9fdb81 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/bargain/AppBargainRecordController.java +++ b/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; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.context.annotation.Lazy; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -46,18 +47,23 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti public class AppBargainRecordController { @Resource + private BargainHelpService bargainHelpService; + @Resource + @Lazy private BargainRecordService bargainRecordService; @Resource + @Lazy private BargainActivityService bargainActivityService; - @Resource - private BargainHelpService bargainHelpService; + @Resource + private TradeOrderApi tradeOrderApi; + @Resource + @Lazy private MemberUserApi memberUserApi; @Resource + @Lazy private ProductSpuApi productSpuApi; - @Resource - private TradeOrderApi tradeOrderApi; @GetMapping("/get-summary") @Operation(summary = "获得砍价记录的概要信息", description = "用于小程序首页") diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java index 1c8004d352..684c7ce9b6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationRecordController.java @@ -46,8 +46,8 @@ public class AppCombinationRecordController { @Operation(summary = "获得拼团记录的概要信息", description = "用于小程序首页") public CommonResult getCombinationRecordSummary() { AppCombinationRecordSummaryRespVO summary = new AppCombinationRecordSummaryRespVO(); - // 1. 获得拼团记录数量 - Long count = combinationRecordService.getCombinationRecordCount(null, null); + // 1. 获得拼团参与用户数量 + Long count = combinationRecordService.getCombinationRecordCount(null, null, null); if (count == 0) { summary.setAvatars(Collections.emptyList()); summary.setUserCount(count); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java index a344b48fcd..03cac269f1 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java @@ -28,7 +28,7 @@ import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; -import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -114,9 +114,6 @@ public interface CombinationActivityConvert { ProductSpuRespDTO spu, ProductSkuRespDTO sku) { return convert(reqDTO).setVirtualGroup(false) .setStatus(CombinationRecordStatusEnum.IN_PROGRESS.getStatus()) // 创建后默认状态为进行中 - .setStartTime(LocalDateTime.now()) // TODO @puhui999:想了下,这个 startTime 应该是团长的; - // TODO @puhui999:有团长的情况下,expireTime 应该是团长的; - .setExpireTime(activity.getStartTime().plusHours(activity.getLimitDuration())) .setUserSize(activity.getUserSize()).setUserCount(1) // 默认就是 1 插入后会接着更新一次所有的拼团记录 // 用户信息 .setNickname(user.getNickname()).setAvatar(user.getAvatar()) @@ -200,4 +197,35 @@ public interface CombinationActivityConvert { return respVO; } + /** + * 转换生成虚拟成团虚拟记录 + * + * @param virtualGroupHeadRecords 虚拟成团团长记录列表 + * @return 虚拟记录列表 + */ + default List convertVirtualGroupList(List virtualGroupHeadRecords) { + List createRecords = new ArrayList<>(); + virtualGroupHeadRecords.forEach(headRecord -> { + // 计算需要创建的虚拟成团记录数量 + int count = headRecord.getUserSize() - headRecord.getUserCount(); + for (int i = 0; i < count; i++) { + // 基础信息和团长保持一致 + CombinationRecordDO newRecord = new CombinationRecordDO().setActivityId(headRecord.getActivityId()) + .setCombinationPrice(headRecord.getCombinationPrice()).setSpuId(headRecord.getSpuId()).setSpuName(headRecord.getSpuName()) + .setPicUrl(headRecord.getPicUrl()).setSkuId(headRecord.getSkuId()).setHeadId(headRecord.getId()) + .setStatus(headRecord.getStatus()) // 状态保持和创建时一致,创建完成后会接着处理 + .setVirtualGroup(headRecord.getVirtualGroup()).setExpireTime(headRecord.getExpireTime()) + .setStartTime(headRecord.getStartTime()).setUserSize(headRecord.getUserSize()).setUserCount(headRecord.getUserCount()); + // 虚拟信息 + newRecord.setCount(0); + newRecord.setUserId(0L); + newRecord.setNickname(""); + newRecord.setAvatar(""); + newRecord.setOrderId(0L); + createRecords.add(newRecord); + } + }); + return createRecords; + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java index d12056d561..ea62b2005a 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/bargain/BargainActivityMapper.java +++ b/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; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.bargain.vo.activity.BargainActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.bargain.BargainActivityDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Param; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 砍价活动 Mapper @@ -85,25 +87,25 @@ public interface BargainActivityMapper extends BaseMapperX { .last("LIMIT " + count)); } - // TODO @puhui999:一个商品,在统一时间,不会参与多个活动;so 是不是不用 inner join 哈? - // PS:如果可以参与多个,其实可以这样写 select * from promotion_bargain_activity group by spu_id ORDER BY create_time DESC;通过 group 来过滤 /** - * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 * * @param spuIds spu 编号 * @param status 状态 - * @return 砍价活动列表 + * @return 包含 spuId 和 activityId 的 map 对象列表 */ - @Select("SELECT p1.* " + - "FROM promotion_bargain_activity p1 " + - "INNER JOIN ( " + - " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " + - " FROM promotion_bargain_activity " + - " WHERE spu_id IN #{spuIds} " + - " GROUP BY spu_id " + - ") p2 " + - "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " + - "ORDER BY p1.create_time DESC;") - List selectListBySpuIds(Collection spuIds, Integer status); + default List> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection spuIds, @Param("status") Integer status) { + return selectMaps(new QueryWrapper() + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id + .in("spu_id", spuIds) + .eq("status", status) + .groupBy("spu_id")); + } + + default List selectListByIds(Collection ids) { + return selectList(new LambdaQueryWrapperX() + .in(BargainActivityDO::getId, ids) + .orderByDesc(BargainActivityDO::getCreateTime)); + } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java index 1af92b5382..a2d15e7f3c 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationActivityMapper.java @@ -6,12 +6,13 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.CombinationActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 拼团活动 Mapper @@ -43,24 +44,24 @@ public interface CombinationActivityMapper extends BaseMapperX selectListBySpuIds(@Param("spuIds") Collection spuIds, @Param("status") Integer status); + default List> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection spuIds, @Param("status") Integer status) { + return selectMaps(new QueryWrapper() + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id + .in("spu_id", spuIds) + .eq("status", status) + .groupBy("spu_id")); + } + + default List selectListByIds(Collection ids) { + return selectList(new LambdaQueryWrapperX() + .in(CombinationActivityDO::getId, ids) + .orderByDesc(CombinationActivityDO::getCreateTime)); + } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java index 31eb7f8273..c232fb9d95 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/combination/CombinationRecordMapper.java @@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.ibatis.annotations.Mapper; +import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -100,13 +102,40 @@ public interface CombinationRecordMapper extends BaseMapperX() - .eq(status != null || virtualGroup != null, - CombinationRecordDO::getHeadId, CombinationRecordDO.HEAD_ID_GROUP) // 统计团信息则指定团长 - .eqIfPresent(CombinationRecordDO::getStatus, status) - .eqIfPresent(CombinationRecordDO::getVirtualGroup, virtualGroup)); + default PageResult selectPage(CombinationRecordReqPage2VO pageVO) { + return selectPage(pageVO, new LambdaQueryWrapperX() + .eq(CombinationRecordDO::getId, pageVO.getHeadId()) + .or() + .eq(CombinationRecordDO::getHeadId, pageVO.getHeadId())); + } + + /** + * 查询指定条件的记录数 + * 如果参数都为 null 时则查询用户拼团记录(DISTINCT 去重),也就是说查询会员表中的用户有多少人参与过拼团活动每个人只统计一次 + * + * @param status 状态,可为 null + * @param virtualGroup 是否虚拟成团,可为 null + * @param headId 团长编号,可为 null + * @return 记录数 + */ + default Long selectCountByHeadAndStatusAndVirtualGroup(Integer status, Boolean virtualGroup, Long headId) { + return selectCount(new QueryWrapper() + .select(status == null && virtualGroup == null && headId == null, "DISTINCT (user_id)") + .eq(status != null, "status", status) + .eq(virtualGroup != null, "virtual_group", virtualGroup) + .eq(headId != null, "head_id", headId) + .groupBy("user_id")); + } + + default List selectListByHeadIdAndStatusAndExpireTimeLt(Long headId, Integer status, LocalDateTime dateTime) { + return selectList(new LambdaQueryWrapperX() + .eq(CombinationRecordDO::getHeadId, headId) + .eq(CombinationRecordDO::getStatus, status) + .lt(CombinationRecordDO::getExpireTime, dateTime)); + } + + default List selectListByHeadIds(Collection headIds) { + return selectList(new LambdaQueryWrapperX().in(CombinationRecordDO::getHeadId, headIds)); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java index b3b75d36b6..528ea07f9d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -7,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Param; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 秒杀活动 Mapper @@ -37,18 +40,32 @@ public interface SeckillActivityMapper extends BaseMapperX { } /** - * 更新活动库存 + * 更新活动库存(减少) * * @param id 活动编号 - * @param count 扣减的库存数量 + * @param count 扣减的库存数量(正数) * @return 影响的行数 */ - default int updateStock(Long id, int count) { + default int updateStockDecr(Long id, int count) { + Assert.isTrue(count > 0); return update(null, new LambdaUpdateWrapper() .eq(SeckillActivityDO::getId, id) - .gt(SeckillActivityDO::getTotalStock, 0) - .setSql("stock = stock + " + count) - .setSql("total_stock = total_stock - " + count)); + .gt(SeckillActivityDO::getStock, count) + .setSql("stock = stock - " + count)); + } + + /** + * 更新活动库存(增加) + * + * @param id 活动编号 + * @param count 增加的库存数量(正数) + * @return 影响的行数 + */ + default int updateStockIncr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(SeckillActivityDO::getId, id) + .setSql("stock = stock + " + count)); } default PageResult selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { @@ -58,24 +75,25 @@ public interface SeckillActivityMapper extends BaseMapperX { .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); } - // TODO @puhui999:类似 BargainActivityMapper /** - * 获取指定 spu 编号最近参加的活动,每个 spuId 只返回一条记录 + * 查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 * * @param spuIds spu 编号 * @param status 状态 - * @return 秒杀活动列表 + * @return 包含 spuId 和 activityId 的 map 对象列表 */ - @Select("SELECT p1.* " + - "FROM promotion_seckill_activity p1 " + - "INNER JOIN ( " + - " SELECT spu_id, MAX(DISTINCT(create_time)) AS max_create_time " + - " FROM promotion_seckill_activity " + - " WHERE spu_id IN #{spuIds} " + - " GROUP BY spu_id " + - ") p2 " + - "ON p1.spu_id = p2.spu_id AND p1.create_time = p2.max_create_time AND p1.status = #{status} " + - "ORDER BY p1.create_time DESC;") - List selectListBySpuIds(Collection spuIds, Integer status); + default List> selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(@Param("spuIds") Collection spuIds, @Param("status") Integer status) { + return selectMaps(new QueryWrapper() + .select("spu_id AS spuId, MAX(DISTINCT(id)) AS activityId") // 时间越大 id 也越大 直接用 id + .in("spu_id", spuIds) + .eq("status", status) + .groupBy("spu_id")); + } + + default List selectListByIds(Collection ids) { + return selectList(new LambdaQueryWrapperX() + .in(SeckillActivityDO::getId, ids) + .orderByDesc(SeckillActivityDO::getCreateTime)); + } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java index 35ea110cea..8fb1401797 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity; +import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -30,17 +31,32 @@ public interface SeckillProductMapper extends BaseMapperX { } /** - * 更新活动库存 + * 更新活动库存(减少) * * @param id 活动编号 - * @param count 扣减的库存数量 + * @param count 扣减的库存数量(减少库存) * @return 影响的行数 */ - default int updateStock(Long id, int count) { + default int updateStockDecr(Long id, int count) { + Assert.isTrue(count > 0); return update(null, new LambdaUpdateWrapper() .eq(SeckillProductDO::getId, id) - .gt(SeckillProductDO::getStock, count) + .ge(SeckillProductDO::getStock, count) .setSql("stock = stock - " + count)); } + /** + * 更新活动库存(增加) + * + * @param id 活动编号 + * @param count 需要增加的库存(增加库存) + * @return 影响的行数 + */ + default int updateStockIncr(Long id, int count) { + Assert.isTrue(count > 0); + return update(null, new LambdaUpdateWrapper() + .eq(SeckillProductDO::getId, id) + .setSql("stock = stock + " + count)); + } + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java new file mode 100644 index 0000000000..2ee6e8b63f --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/job/combination/CombinationRecordExpireJob.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.promotion.job.combination; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 拼团过期 Job + * + * @author HUIHUI + */ +@Component +public class CombinationRecordExpireJob implements JobHandler { + + @Resource + private CombinationRecordService combinationRecordService; + + @Override + @TenantJob + public String execute(String param) throws Exception { + KeyValue keyValue = combinationRecordService.expireCombinationRecord(); + return StrUtil.format("过期拼团 {} 个, 虚拟成团 {} 个", keyValue.getKey(), keyValue.getValue()); + } + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java index 0905a09a76..364cdf9ee9 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/bargain/BargainActivityServiceImpl.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.promotion.service.bargain; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -20,12 +22,11 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; @@ -84,11 +85,16 @@ public class BargainActivityServiceImpl implements BargainActivityService { @Override public void updateBargainActivityStock(Long id, Integer count) { - // 更新库存。如果更新失败,则抛出异常 - int updateCount = bargainActivityMapper.updateStock(id, count); - if (updateCount == 0) { - throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH); + if (count < 0) { + // 更新库存。如果更新失败,则抛出异常 + int updateCount = bargainActivityMapper.updateStock(id, count); + if (updateCount == 0) { + throw exception(BARGAIN_ACTIVITY_STOCK_NOT_ENOUGH); + } + } else if (count > 0) { + bargainActivityMapper.updateStock(id, count); } + } private void validateBargainConflict(Long spuId, Long activityId) { @@ -139,7 +145,7 @@ public class BargainActivityServiceImpl implements BargainActivityService { @Override public List getBargainActivityList(Set ids) { - return bargainActivityMapper.selectBatchIds(ids); + return bargainActivityMapper.selectBatchIds(ids); } @Override @@ -178,7 +184,13 @@ public class BargainActivityServiceImpl implements BargainActivityService { @Override public List getBargainActivityBySpuIdsAndStatus(Collection spuIds, Integer status) { - return bargainActivityMapper.selectListBySpuIds(spuIds, status); + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 + List> spuIdAndActivityIdMaps = bargainActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status); + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) { + return Collections.emptyList(); + } + // 2.查询活动详情 + return bargainActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId"))); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java index db907ba363..dae388410a 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.promotion.service.combination; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -25,12 +26,12 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; @@ -228,7 +229,13 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic @Override public List getCombinationActivityBySpuIdsAndStatus(Collection spuIds, Integer status) { - return combinationActivityMapper.selectListBySpuIds(spuIds, status); + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 + List> spuIdAndActivityIdMaps = combinationActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status); + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) { + return Collections.emptyList(); + } + // 2.查询活动详情 + return combinationActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId"))); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java index dd07a8b935..68cdde7607 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO; @@ -49,9 +50,9 @@ public interface CombinationRecordService { * 创建拼团记录 * * @param reqDTO 创建信息 - * @return 开团记录编号 + * @return key 开团记录编号 value 团长编号 */ - Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO); + KeyValue createCombinationRecord(CombinationRecordCreateReqDTO reqDTO); /** * 获得拼团记录 @@ -90,9 +91,10 @@ public interface CombinationRecordService { * * @param status 状态-允许为空 * @param virtualGroup 是否虚拟成团-允许为空 + * @param headId 团长编号,允许空。目的 headId 设置为 {@link CombinationRecordDO#HEAD_ID_GROUP} 时,可以设置 * @return 记录数 */ - Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup); + Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId); /** * 获取最近的 count 条拼团记录 @@ -136,6 +138,15 @@ public interface CombinationRecordService { */ PageResult getCombinationRecordPage(CombinationRecordReqPageVO pageVO); + /** + * 获取拼团记录分页数据(通过团长查询) + * + * @param pageVO 分页请求 + * @return 拼团记录分页数据(包括团长的) + */ + PageResult getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO); + + /** * 【拼团活动】获得拼团记录数量 Map * @@ -167,5 +178,11 @@ public interface CombinationRecordService { */ void cancelCombinationRecord(Long userId, Long id, Long headId); + /** + * 处理过期拼团 + * + * @return key 过期拼团数量, value 虚拟成团数量 + */ + KeyValue expireCombinationRecord(); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index e0548e7e1a..76122fa39f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.service.combination; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO; import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO; +import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPage2VO; import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.recrod.CombinationRecordReqPageVO; import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert; import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO; @@ -22,17 +24,18 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecord import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Nullable; import javax.annotation.Resource; +import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; @@ -52,7 +55,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @Lazy private CombinationActivityService combinationActivityService; @Resource - private CombinationRecordMapper recordMapper; + private CombinationRecordMapper combinationRecordMapper; @Resource private MemberUserApi memberUserApi; @@ -62,6 +65,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @Resource @Lazy private ProductSkuApi productSkuApi; + @Resource private TradeOrderApi tradeOrderApi; @@ -74,12 +78,12 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { // 更新状态 record.setStatus(status); - recordMapper.updateById(record); + combinationRecordMapper.updateById(record); } private CombinationRecordDO validateCombinationRecord(Long userId, Long orderId) { // 校验拼团是否存在 - CombinationRecordDO recordDO = recordMapper.selectByUserIdAndOrderId(userId, orderId); + CombinationRecordDO recordDO = combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId); if (recordDO == null) { throw exception(COMBINATION_RECORD_NOT_EXISTS); } @@ -108,7 +112,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { // 2. 父拼团是否存在,是否已经满了 if (headId != null) { // 2.1. 查询进行中的父拼团 - CombinationRecordDO record = recordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); + CombinationRecordDO record = combinationRecordMapper.selectByHeadId(headId, CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); if (record == null) { throw exception(COMBINATION_RECORD_HEAD_NOT_EXISTS); } @@ -143,7 +147,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { } // 6.1 校验是否有拼团记录 - List recordList = recordMapper.selectListByUserIdAndActivityId(userId, activityId); + List recordList = combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId); recordList.removeIf(record -> CombinationRecordStatusEnum.isFailed(record.getStatus())); // 取消的订单,不算数 if (CollUtil.isEmpty(recordList)) { // 如果为空,说明可以参与,直接返回 return new KeyValue<>(activity, product); @@ -164,32 +168,36 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { @Override @Transactional(rollbackFor = Exception.class) - public Long createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { - // 1. 校验拼团活动 + public KeyValue createCombinationRecord(CombinationRecordCreateReqDTO reqDTO) { + // 1.校验拼团活动 KeyValue keyValue = validateCombinationRecord(reqDTO.getUserId(), reqDTO.getActivityId(), reqDTO.getHeadId(), reqDTO.getSkuId(), reqDTO.getCount()); - // 2.1 组合数据创建拼团记录 + // 2.组合数据创建拼团记录 MemberUserRespDTO user = memberUserApi.getUser(reqDTO.getUserId()); ProductSpuRespDTO spu = productSpuApi.getSpu(reqDTO.getSpuId()); ProductSkuRespDTO sku = productSkuApi.getSku(reqDTO.getSkuId()); CombinationRecordDO record = CombinationActivityConvert.INSTANCE.convert(reqDTO, keyValue.getKey(), user, spu, sku); - // 2.2 如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP + // 2.1.如果是团长需要设置 headId 为 CombinationRecordDO#HEAD_ID_GROUP if (record.getHeadId() == null) { - record.setHeadId(CombinationRecordDO.HEAD_ID_GROUP); + record.setStartTime(LocalDateTime.now()) + .setExpireTime(keyValue.getKey().getStartTime().plusHours(keyValue.getKey().getLimitDuration())) + .setHeadId(CombinationRecordDO.HEAD_ID_GROUP); + } else { + // 2.2.有团长的情况下需要设置开始时间和过期时间为团长的 + CombinationRecordDO headRecord = combinationRecordMapper.selectByHeadId(record.getHeadId(), + CombinationRecordStatusEnum.IN_PROGRESS.getStatus()); // 查询进行中的父拼团 + record.setStartTime(headRecord.getStartTime()).setExpireTime(headRecord.getExpireTime()); } - recordMapper.insert(record); + combinationRecordMapper.insert(record); if (ObjUtil.equal(CombinationRecordDO.HEAD_ID_GROUP, record.getHeadId())) { - return record.getId(); + return new KeyValue<>(record.getId(), record.getHeadId()); } - // TODO @puhui:是不是这里的更新,放到 order 模块那;支付完成后; - // 4、更新拼团相关信息到订单 - tradeOrderApi.updateOrderCombinationInfo(record.getOrderId(), record.getActivityId(), record.getId(), record.getHeadId()); - // 4、更新拼团记录 + // 3、更新拼团记录 updateCombinationRecordWhenCreate(reqDTO.getHeadId(), keyValue.getKey()); - return record.getId(); + return new KeyValue<>(record.getId(), record.getHeadId()); } /** @@ -204,31 +212,33 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { if (CollUtil.isEmpty(records)) { return; } - CombinationRecordDO headRecord = recordMapper.selectById(headId); + CombinationRecordDO headRecord = combinationRecordMapper.selectById(headId); // 2. 批量更新记录 List updateRecords = new ArrayList<>(); records.add(headRecord); // 加入团长,团长也需要更新 boolean isFull = records.size() >= activity.getUserSize(); + LocalDateTime now = LocalDateTime.now(); records.forEach(item -> { CombinationRecordDO updateRecord = new CombinationRecordDO(); updateRecord.setId(item.getId()).setUserCount(records.size()); if (isFull) { updateRecord.setStatus(CombinationRecordStatusEnum.SUCCESS.getStatus()); + updateRecord.setEndTime(now); } updateRecords.add(updateRecord); }); - recordMapper.updateBatch(updateRecords); + combinationRecordMapper.updateBatch(updateRecords); } @Override public CombinationRecordDO getCombinationRecord(Long userId, Long orderId) { - return recordMapper.selectByUserIdAndOrderId(userId, orderId); + return combinationRecordMapper.selectByUserIdAndOrderId(userId, orderId); } @Override public List getCombinationRecordListByUserIdAndActivityId(Long userId, Long activityId) { - return recordMapper.selectListByUserIdAndActivityId(userId, activityId); + return combinationRecordMapper.selectListByUserIdAndActivityId(userId, activityId); } @Override @@ -241,52 +251,57 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { } @Override - public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup) { - return recordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup); + public Long getCombinationRecordCount(@Nullable Integer status, @Nullable Boolean virtualGroup, @Nullable Long headId) { + return combinationRecordMapper.selectCountByHeadAndStatusAndVirtualGroup(status, virtualGroup, headId); } @Override public List getLatestCombinationRecordList(int count) { - return recordMapper.selectLatestList(count); + return combinationRecordMapper.selectLatestList(count); } @Override public List getHeadCombinationRecordList(Long activityId, Integer status, Integer count) { - return recordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status, + return combinationRecordMapper.selectListByActivityIdAndStatusAndHeadId(activityId, status, CombinationRecordDO.HEAD_ID_GROUP, count); } @Override public CombinationRecordDO getCombinationRecordById(Long id) { - return recordMapper.selectById(id); + return combinationRecordMapper.selectById(id); } @Override public List getCombinationRecordListByHeadId(Long headId) { - return recordMapper.selectList(CombinationRecordDO::getHeadId, headId); + return combinationRecordMapper.selectList(CombinationRecordDO::getHeadId, headId); } @Override public PageResult getCombinationRecordPage(CombinationRecordReqPageVO pageVO) { - return recordMapper.selectPage(pageVO); + return combinationRecordMapper.selectPage(pageVO); + } + + @Override + public PageResult getCombinationRecordPage2(CombinationRecordReqPage2VO pageVO) { + return combinationRecordMapper.selectPage(pageVO); } @Override public Map getCombinationRecordCountMapByActivity(Collection activityIds, @Nullable Integer status, @Nullable Long headId) { - return recordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId); + return combinationRecordMapper.selectCombinationRecordCountMapByActivityIdAndStatusAndHeadId(activityIds, status, headId); } @Override public CombinationRecordDO getCombinationRecordByIdAndUser(Long userId, Long id) { - return recordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id); + return combinationRecordMapper.selectOne(CombinationRecordDO::getUserId, userId, CombinationRecordDO::getId, id); } @Override @Transactional(rollbackFor = Exception.class) public void cancelCombinationRecord(Long userId, Long id, Long headId) { // 删除记录 - recordMapper.deleteById(id); + combinationRecordMapper.deleteById(id); // 需要更新的记录 List updateRecords = new ArrayList<>(); @@ -313,7 +328,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { }); } else { // 情况二:团员 // 团长 - CombinationRecordDO recordHead = recordMapper.selectById(headId); + CombinationRecordDO recordHead = combinationRecordMapper.selectById(headId); // 团员 List records = getCombinationRecordListByHeadId(headId); if (CollUtil.isEmpty(records)) { @@ -329,7 +344,112 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { } // 更新拼团记录 - recordMapper.updateBatch(updateRecords); + combinationRecordMapper.updateBatch(updateRecords); + } + + @Override + public KeyValue expireCombinationRecord() { + // 1。获取所有正在进行中的过期的父拼团 + List headExpireRecords = combinationRecordMapper.selectListByHeadIdAndStatusAndExpireTimeLt( + CombinationRecordDO.HEAD_ID_GROUP, CombinationRecordStatusEnum.IN_PROGRESS.getStatus(), LocalDateTime.now()); + if (CollUtil.isEmpty(headExpireRecords)) { + return new KeyValue<>(0, 0); + } + + // 2.获取拼团活动 + List combinationActivities = combinationActivityService.getCombinationActivityListByIds( + convertSet(headExpireRecords, CombinationRecordDO::getActivityId)); + Map activityMap = convertMap(combinationActivities, CombinationActivityDO::getId); + + // 3.校验是否虚拟成团 + List virtualGroupHeadRecords = new ArrayList<>(); // 虚拟成团 + for (Iterator iterator = headExpireRecords.iterator(); iterator.hasNext(); ) { + CombinationRecordDO record = iterator.next(); + // 3.1 不匹配,则直接跳过 + CombinationActivityDO activityDO = activityMap.get(record.getActivityId()); + if (activityDO == null || !activityDO.getVirtualGroup()) { // 取不到活动的或者不是虚拟拼团的 + continue; + } + // 3.2 匹配,则移除,添加到虚拟成团中,并结束寻找 + virtualGroupHeadRecords.add(record); + iterator.remove(); + break; + } + + // 4.处理过期的拼团 + getSelf().handleExpireRecord(headExpireRecords); + // 5.虚拟成团 + getSelf().handleVirtualGroupRecord(virtualGroupHeadRecords); + + return new KeyValue<>(headExpireRecords.size(), virtualGroupHeadRecords.size()); + } + + @Async + protected void handleExpireRecord(List headExpireRecords) { + if (CollUtil.isEmpty(headExpireRecords)) { + return; + } + + // 1.更新拼团记录 + List headsAndRecords = updateBatchCombinationRecords(headExpireRecords, + CombinationRecordStatusEnum.FAILED); + if (headsAndRecords == null) { + return; + } + + // 2.订单取消 TODO 以现在的取消回滚逻辑好像只能循环了 + headsAndRecords.forEach(item -> { + tradeOrderApi.cancelPaidOrder(item.getUserId(), item.getOrderId()); + }); + + } + + @Async + protected void handleVirtualGroupRecord(List virtualGroupHeadRecords) { + if (CollUtil.isEmpty(virtualGroupHeadRecords)) { + return; + } + + // 1.团员补齐 + combinationRecordMapper.insertBatch(CombinationActivityConvert.INSTANCE.convertVirtualGroupList(virtualGroupHeadRecords)); + // 2.更新拼团记录 + updateBatchCombinationRecords(virtualGroupHeadRecords, CombinationRecordStatusEnum.SUCCESS); + } + + private List updateBatchCombinationRecords(List headRecords, CombinationRecordStatusEnum status) { + // 1. 查询团成员 + List records = combinationRecordMapper.selectListByHeadIds( + convertSet(headRecords, CombinationRecordDO::getId)); + if (CollUtil.isEmpty(records)) { + return null; + } + Map> recordsMap = convertMultiMap(records, CombinationRecordDO::getHeadId); + headRecords.forEach(item -> { + recordsMap.get(item.getId()).add(item); // 把团长加进团里 + }); + // 2.批量更新拼团记录 status 和 失败/成团时间 + List headsAndRecords = mergeValuesFromMap(recordsMap); + List updateRecords = new ArrayList<>(headsAndRecords.size()); + LocalDateTime now = LocalDateTime.now(); + headsAndRecords.forEach(item -> { + CombinationRecordDO record = new CombinationRecordDO().setId(item.getId()) + .setStatus(status.getStatus()).setEndTime(now); + if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数 + record.setUserCount(record.getUserSize()); + } + updateRecords.add(record); + }); + combinationRecordMapper.updateBatch(updateRecords); + return headsAndRecords; + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private CombinationRecordServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); } } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java index f71c538a82..36074fd563 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java @@ -36,13 +36,22 @@ public interface SeckillActivityService { void updateSeckillActivity(@Valid SeckillActivityUpdateReqVO updateReqVO); /** - * 更新秒杀库存 + * 更新秒杀库存(减少) * * @param id 活动编号 * @param skuId sku 编号 - * @param count 数量 + * @param count 数量(正数) */ - void updateSeckillStock(Long id, Long skuId, Integer count); + void updateSeckillStockDecr(Long id, Long skuId, Integer count); + + /** + * 更新秒杀库存(增加) + * + * @param id 活动编号 + * @param skuId sku 编号 + * @param count 数量(正数) + */ + void updateSeckillStockIncr(Long id, Long skuId, Integer count); /** * 关闭秒杀活动 @@ -113,8 +122,8 @@ public interface SeckillActivityService { * 如果校验失败,则抛出业务异常 * * @param activityId 活动编号 - * @param skuId SKU 编号 - * @param count 数量 + * @param skuId SKU 编号 + * @param count 数量 * @return 秒杀信息 */ SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java index d2bbc7466d..6d86bd517e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.promotion.service.seckill; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -27,6 +29,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -154,7 +157,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @Override @Transactional(rollbackFor = Exception.class) - public void updateSeckillStock(Long id, Long skuId, Integer count) { + public void updateSeckillStockDecr(Long id, Long skuId, Integer count) { // 1.1 校验活动库存是否充足 SeckillActivityDO seckillActivity = validateSeckillActivityExists(id); if (count > seckillActivity.getTotalStock()) { @@ -167,18 +170,28 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { } // 2.1 更新活动商品库存 - int updateCount = seckillProductMapper.updateStock(product.getId(), count); + int updateCount = seckillProductMapper.updateStockDecr(product.getId(), count); if (updateCount == 0) { throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); } // 2.2 更新活动库存 - updateCount = seckillActivityMapper.updateStock(seckillActivity.getId(), count); + updateCount = seckillActivityMapper.updateStockDecr(seckillActivity.getId(), count); if (updateCount == 0) { throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); } } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSeckillStockIncr(Long id, Long skuId, Integer count) { + SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(id, skuId); + // 更新活动商品库存 + seckillProductMapper.updateStockIncr(product.getId(), count); + // 更新活动库存 + seckillActivityMapper.updateStockIncr(id, count); + } + /** * 更新秒杀商品 * @@ -312,7 +325,13 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { @Override public List getSeckillActivityBySpuIdsAndStatus(Collection spuIds, Integer status) { - return seckillActivityMapper.selectListBySpuIds(spuIds, status); + // 1.查询出指定 spuId 的 spu 参加的活动最接近现在的一条记录。多个的话,一个 spuId 对应一个最近的活动编号 + List> spuIdAndActivityIdMaps = seckillActivityMapper.selectSpuIdAndActivityIdMapsBySpuIdsAndStatus(spuIds, status); + if (CollUtil.isEmpty(spuIdAndActivityIdMaps)) { + return Collections.emptyList(); + } + // 2.查询活动详情 + return seckillActivityMapper.selectListByIds(convertSet(spuIdAndActivityIdMaps, map -> MapUtil.getLong(map, "activityId"))); } } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 9c11aa9ad8..33081d4614 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java @@ -33,7 +33,8 @@ public interface ErrorCodeConstants { ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元"); ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态"); ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】"); - ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单已发货"); + ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态"); + ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单"); // ========== After Sale 模块 1-011-000-100 ========== ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在"); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java index a4c7f1447a..87489972b8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApiImpl.java +++ b/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.convert.order.TradeOrderConvert; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -24,10 +25,11 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_F @Validated public class TradeOrderApiImpl implements TradeOrderApi { - @Resource - private TradeOrderQueryService tradeOrderQueryService; @Resource private TradeOrderUpdateService tradeOrderUpdateService; + @Resource + @Lazy + private TradeOrderQueryService tradeOrderQueryService; @Override public List getOrderList(Collection ids) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java index 8dc29e8cdf..20bd9cb7f8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java @@ -93,4 +93,16 @@ public interface TradeOrderMapper extends BaseMapperX { return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode); } + default TradeOrderDO selectByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) { + return selectOne(new LambdaQueryWrapperX() + .and(q -> q.eq(TradeOrderDO::getUserId, userId) + .eq(TradeOrderDO::getStatus, status)) + .and(q -> q.eq(TradeOrderDO::getCombinationActivityId, activityId) + .or() + .eq(TradeOrderDO::getSeckillActivityId, activityId) + .or() + .eq(TradeOrderDO::getBargainActivityId, activityId)) + ); + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java index 1cbe025ec0..c5d369490a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java @@ -38,6 +38,16 @@ public interface TradeOrderQueryService { */ TradeOrderDO getOrder(Long userId, Long id); + /** + * 获得指定用户,指定活动,指定状态的交易订单 + * + * @param userId 用户编号 + * @param activityId 活动编号 + * @param status 订单状态 + * @return 交易订单 + */ + TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status); + /** * 获得订单列表 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java index 7da2f79b45..c27756e3a1 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java @@ -70,6 +70,11 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { return order; } + @Override + public TradeOrderDO getActivityOrderByUserIdAndActivityIdAndStatus(Long userId, Long activityId, Integer status) { + return tradeOrderMapper.selectByUserIdAndActivityIdAndStatus(userId, activityId, status); + } + @Override public List getOrderList(Collection ids) { if (CollUtil.isEmpty(ids)) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 74a23ff264..352d0083cb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -14,23 +14,12 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.module.member.api.address.AddressApi; import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; -import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; -import cn.iocoder.yudao.module.member.api.point.MemberPointApi; -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; -import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; -import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum; -import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; -import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; @@ -40,7 +29,6 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; -import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; @@ -48,15 +36,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO; -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import cn.iocoder.yudao.module.trade.enums.order.*; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog; import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils; -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService; -import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; -import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO; import cn.iocoder.yudao.module.trade.service.cart.CartService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; import cn.iocoder.yudao.module.trade.service.message.TradeMessageService; @@ -68,13 +52,15 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -109,30 +95,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { private DeliveryExpressService deliveryExpressService; @Resource private TradeMessageService tradeMessageService; - @Resource - private BrokerageUserService brokerageUserService; - @Resource - private BrokerageRecordService brokerageRecordService; - @Resource - private ProductSpuApi productSpuApi; - @Resource - private ProductSkuApi productSkuApi; @Resource private PayOrderApi payOrderApi; @Resource private AddressApi addressApi; @Resource - private CouponApi couponApi; - @Resource - private CombinationRecordApi combinationRecordApi; - @Resource - private MemberUserApi memberUserApi; - @Resource - private MemberLevelApi memberLevelApi; - @Resource - private MemberPointApi memberPointApi; - @Resource private ProductCommentApi productCommentApi; @Resource @@ -199,7 +167,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { List orderItems = buildTradeOrderItems(order, calculateRespBO); // 2. 订单创建前的逻辑 - beforeCreateTradeOrder(order, orderItems); + tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems)); // 3. 保存订单 tradeOrderMapper.insert(order); @@ -234,11 +202,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile()); order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位 } - // 设置订单推广人 - BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId()); - if (brokerageUser != null && brokerageUser.getBindUserId() != null) { - order.setBrokerageUserId(brokerageUser.getBindUserId()); - } return order; } @@ -247,21 +210,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { return TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); } - /** - * 订单创建前,执行前置逻辑 - * - * @param order 订单 - * @param orderItems 订单项 - */ - private void beforeCreateTradeOrder(TradeOrderDO order, List orderItems) { - // 1. 执行订单创建前置处理器 - // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等 - tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems)); - - // 2. 下单时扣减商品库存 - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems)); - } - /** * 订单创建后,执行后置逻辑 *

@@ -276,27 +224,16 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { // 1. 执行订单创建后置处理器 tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems)); - // 2. 有使用优惠券时更新 - // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号 - if (order.getCouponId() != null) { - couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId()) - .setOrderId(order.getId())); - } - - // 3. 扣减积分(抵扣) - // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务 - reduceUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId()); - - // 4. 删除购物车商品 + // 2. 删除购物车商品 Set cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); if (CollUtil.isNotEmpty(cartIds)) { cartService.deleteCart(order.getUserId(), cartIds); } - // 5. 生成预支付 + // 3. 生成预支付 createPayOrder(order, orderItems); - // 6. 插入订单日志 + // 4. 插入订单日志 TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus()); // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! @@ -330,18 +267,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID); } - // 3、订单支付成功后 + // 3. 执行 TradeOrderHandler 的后置处理 List orderItems = tradeOrderItemMapper.selectListByOrderId(id); tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems)); - // 4.1 增加用户积分(赠送) - addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId()); - // 4.2 增加用户经验 - getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId()); - // 4.3 增加用户佣金 - getSelf().addBrokerageAsync(order.getUserId(), order.getId()); - - // 5. 记录订单日志 + // 4. 记录订单日志 TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus()); TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue()); } @@ -434,8 +364,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build()); // 4. 发送站内信 - tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO().setOrderId(order.getId()) - .setUserId(order.getUserId()).setMessage(null)); + tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO() + .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); } /** @@ -448,18 +378,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { */ private TradeOrderDO validateOrderDeliverable(Long id) { TradeOrderDO order = validateOrderExists(id); - // 校验订单是否退款 + // 1. 校验订单是否未发货 if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE); } - // 订单类型:拼团 - if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) { - // 校验订单拼团是否成功 - if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) { - throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS); - } - } + // 2. 执行 TradeOrderHandler 前置处理 + tradeOrderHandlers.forEach(handler -> handler.beforeDeliveryOrder(order)); return order; } @@ -616,30 +541,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { * @param cancelType 取消类型 */ private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) { - Long id = order.getId(); // 1. 更新 TradeOrderDO 状态为已取消 - int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(), + int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus()) .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now())); if (updateCount == 0) { throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); } - List orderItems = tradeOrderItemMapper.selectListByOrderId(id); - // 3. 回滚库存 - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)); - // 3.1、 活动相关的回滚 - tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, orderItems)); + // 2. 执行 TradeOrderHandler 的后置处理 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems)); - // 4. 回滚优惠券 - if (order.getCouponId() != null && order.getCouponId() > 0) { - couponApi.returnUsedCoupon(order.getCouponId()); - } - - // 5. 回滚积分(抵扣的) - addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId()); - - // 6. 增加订单日志 + // 3. 增加订单日志 TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus()); } @@ -660,8 +574,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setStatus(TradeOrderStatusEnum.CANCELED.getStatus()) .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now())); - // 2. 退还优惠券 - couponApi.returnUsedCoupon(order.getCouponId()); + // 2. 执行 TradeOrderHandler 的后置处理 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems)); } @Override @@ -742,9 +657,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) { // 校验交易订单 TradeOrderDO order = validateOrderExists(reqVO.getId()); - // 发货后,不允许修改; - // TODO @puhui999:只有待发货,可以执行 update - if (TradeOrderStatusEnum.isDelivered(order.getStatus())) { + // 只有待发货状态才可以修改订单收货地址; + if (!TradeOrderStatusEnum.isUndelivered(order.getStatus())) { throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED); } @@ -790,13 +704,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Override @Transactional(rollbackFor = Exception.class) public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) { - // 1. 更新订单项 + // 1.1 更新订单项 updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null); - - // 2.1 更新订单的退款金额、积分 + // 1.2 执行 TradeOrderHandler 的后置处理 TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id); TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrderItem(order, orderItem)); + + // 2.1 更新订单的退款金额、积分 Integer orderRefundPrice = order.getRefundPrice() + refundPrice; Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint(); Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ? @@ -807,23 +723,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint)); // 2.2 如果全部退款,则进行取消订单 getSelf().cancelOrderByAfterSale(order, orderRefundPrice); - - - // 3. 回滚库存 - productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem))); - // 3.1、 活动相关的回滚 - tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, Collections.singletonList(orderItem))); - - // 4.1 回滚积分:扣减用户积分(赠送的) - reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId()); - // 4.2 回滚积分:增加用户积分(返还抵扣) - addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId()); - - // 5. 回滚经验:扣减用户经验 - getSelf().reduceUserExperienceAsync(order.getUserId(), refundPrice, orderItem.getAfterSaleId()); - - // 6. 回滚佣金:更新分佣记录为已失效 - getSelf().cancelBrokerageAsync(order.getUserId(), id); } @Override @@ -840,6 +739,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { if (updateCount <= 0) { throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL); } + } /** @@ -940,7 +840,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT) public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) { // 1. 查询未评论的订单项 - List orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(order.getId(), Boolean.FALSE); + List orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus( + order.getId(), Boolean.FALSE); if (CollUtil.isEmpty(orderItems)) { return; } @@ -983,73 +884,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { // =================== 营销相关的操作 =================== - @Async - protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) { - int bizType = MemberExperienceBizTypeEnum.ORDER.getType(); - memberLevelApi.addExperience(userId, payPrice, bizType, String.valueOf(orderId)); - } - - @Async - protected void reduceUserExperienceAsync(Long userId, Integer refundPrice, Long afterSaleId) { - int bizType = MemberExperienceBizTypeEnum.REFUND.getType(); - memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId)); - } - - /** - * 添加用户积分 - *

- * 目前是支付成功后,就会创建积分记录。 - *

- * 业内还有两种做法,可以根据自己的业务调整: - * 1. 确认收货后,才创建积分记录 - * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻 - * - * @param userId 用户编号 - * @param point 增加积分数量 - * @param bizType 业务编号 - * @param bizId 业务编号 - */ - protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) { - if (point != null && point > 0) { - memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId)); - } - } - - protected void reduceUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) { - if (point != null && point > 0) { - memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId)); - } - } - - /** - * 创建分销记录 - *

- * 目前是支付成功后,就会创建分销记录。 - *

- * 业内还有两种做法,可以根据自己的业务调整: - * 1. 确认收货后,才创建分销记录 - * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻 - * - * @param userId 用户编号 - * @param orderId 订单编号 - */ - @Async - protected void addBrokerageAsync(Long userId, Long orderId) { - MemberUserRespDTO user = memberUserApi.getUser(userId); - Assert.notNull(user); - // 每一个订单项,都会去生成分销记录 - List orderItems = tradeOrderItemMapper.selectListByOrderId(orderId); - List addList = convertList(orderItems, - item -> TradeOrderConvert.INSTANCE.convert(user, item, - productSpuApi.getSpu(item.getSpuId()), productSkuApi.getSku(item.getSkuId()))); - brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList); - } - - @Async - protected void cancelBrokerageAsync(Long userId, Long orderItemId) { - brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId)); - } - /** * 获得自身的代理对象,解决 AOP 生效问题 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java deleted file mode 100644 index b83d2771ba..0000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.order.handler; - -import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi; -import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 砍价订单 handler 实现类 - * - * @author HUIHUI - */ -@Component -public class TradeBargainHandler implements TradeOrderHandler { - - @Resource - private BargainActivityApi bargainActivityApi; - @Resource - private BargainRecordApi bargainRecordApi; - - @Override - public void beforeOrderCreate(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isBargain(order.getType())) { - return; - } - - // 扣减砍价活动的库存 - bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), - -orderItems.get(0).getCount()); - } - - @Override - public void afterOrderCreate(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isBargain(order.getType())) { - return; - } - - // 记录砍价记录对应的订单编号 - bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId()); - } - - @Override - public void cancelOrder(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isBargain(order.getType())) { - return; - } - // TODO 芋艿:取消订单时,需要增加库存 - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java new file mode 100644 index 0000000000..9af7be6700 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi; +import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 砍价订单的 {@link TradeOrderHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class TradeBargainOrderHandler implements TradeOrderHandler { + + @Resource + private BargainActivityApi bargainActivityApi; + @Resource + private BargainRecordApi bargainRecordApi; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { + if (!TradeOrderTypeEnum.isBargain(order.getType())) { + return; + } + // 明确校验一下 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品"); + + // 扣减砍价活动的库存 + bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), + -orderItems.get(0).getCount()); + } + + @Override + public void afterOrderCreate(TradeOrderDO order, List orderItems) { + if (!TradeOrderTypeEnum.isBargain(order.getType())) { + return; + } + // 明确校验一下 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品"); + + // 记录砍价记录对应的订单编号 + bargainRecordApi.updateBargainRecordOrderId(order.getBargainRecordId(), order.getId()); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + if (!TradeOrderTypeEnum.isBargain(order.getType())) { + return; + } + // 明确校验一下 + Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品"); + + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } + afterCancelOrderItem(order, orderItems.get(0)); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { + if (!TradeOrderTypeEnum.isBargain(order.getType())) { + return; + } + // 恢复(增加)砍价活动的库存 + bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), orderItem.getCount()); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java new file mode 100644 index 0000000000..6dd37111af --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java @@ -0,0 +1,118 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; +import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; +import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService; +import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; +import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 订单分销的 {@link TradeOrderHandler} 实现类 + * + * @author 芋道源码 + */ +@Component +public class TradeBrokerageOrderHandler implements TradeOrderHandler { + + @Resource + private MemberUserApi memberUserApi; + @Resource + private ProductSpuApi productSpuApi; + @Resource + private ProductSkuApi productSkuApi; + + @Resource + private BrokerageRecordService brokerageRecordService; + @Resource + private BrokerageUserService brokerageUserService; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { + // 设置订单推广人 + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId()); + if (brokerageUser != null && brokerageUser.getBindUserId() != null) { + order.setBrokerageUserId(brokerageUser.getBindUserId()); + } + } + + @Override + public void afterPayOrder(TradeOrderDO order, List orderItems) { + if (order.getBrokerageUserId() == null) { + return; + } + addBrokerage(order.getUserId(), orderItems); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + // 如果是未支付的订单,不会产生分销结果,所以直接 return + if (!order.getPayStatus()) { + return; + } + if (order.getBrokerageUserId() == null) { + return; + } + + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } + orderItems.forEach(orderItem -> afterCancelOrderItem(order, orderItem)); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { + if (order.getBrokerageUserId() == null) { + return; + } + cancelBrokerage(order.getId(), orderItem.getOrderId()); + } + + /** + * 创建分销记录 + *

+ * 目前是支付成功后,就会创建分销记录。 + *

+ * 业内还有两种做法,可以根据自己的业务调整: + * 1. 确认收货后,才创建分销记录 + * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻 + * + * @param userId 用户编号 + * @param orderItems 订单项 + */ + protected void addBrokerage(Long userId, List orderItems) { + MemberUserRespDTO user = memberUserApi.getUser(userId); + Assert.notNull(user); + ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId()); + Assert.notNull(spu); + ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId()); + + // 每一个订单项,都会去生成分销记录 + List addList = convertList(orderItems, + item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku)); + brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList); + } + + protected void cancelBrokerage(Long userId, Long orderItemId) { + brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId)); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java deleted file mode 100644 index 17b01ca0e0..0000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.order.handler; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; -import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 拼团订单 handler 接口实现类 - * - * @author HUIHUI - */ -@Component -public class TradeCombinationHandler implements TradeOrderHandler { - - @Resource - private CombinationRecordApi combinationRecordApi; - - @Override - public void beforeOrderCreate(TradeOrderDO order, List orderItems) { - // 如果不是拼团订单则结束 - if (TradeOrderTypeEnum.isCombination(order.getType())) { - return; - } - Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品"); - - // 校验是否满足拼团活动相关限制 - TradeOrderItemDO item = orderItems.get(0); - combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(), - order.getCombinationHeadId(), item.getSkuId(), item.getCount()); - // TODO @puhui999:这里还要限制下,是不是已经 createOrder;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了; - } - - @Override - public void afterPayOrder(TradeOrderDO order, List orderItems) { - // 如果不是拼团订单则结束 - if (TradeOrderTypeEnum.isCombination(order.getType())) { - return; - } - Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品"); - - // 获取商品信息 - TradeOrderItemDO item = orderItems.get(0); - // 创建拼团记录 - combinationRecordApi.createCombinationRecord(TradeOrderConvert.INSTANCE.convert(order, item)); - } - - @Override - public void cancelOrder(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isCombination(order.getType())) { - return; - } - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java new file mode 100644 index 0000000000..5c00e5a128 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; +import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID; +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS; + +/** + * 拼团订单的 {@link TradeOrderHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class TradeCombinationOrderHandler implements TradeOrderHandler { + + @Resource + @Lazy + private TradeOrderUpdateService orderUpdateService; + @Resource + @Lazy + private TradeOrderQueryService orderQueryService; + + @Resource + @Lazy + private CombinationRecordApi combinationRecordApi; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { + // 如果不是拼团订单则结束 + if (!TradeOrderTypeEnum.isCombination(order.getType())) { + return; + } + Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品"); + + // 1. 校验是否满足拼团活动相关限制 + TradeOrderItemDO item = orderItems.get(0); + combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(), + order.getCombinationHeadId(), item.getSkuId(), item.getCount()); + // 2. 校验该用户是否存在未支付的拼团活动订单;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了; + TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus( + order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus()); + if (activityOrder != null) { + throw exception(ORDER_CREATE_FAIL_EXIST_UNPAID); + } + } + + @Override + public void afterPayOrder(TradeOrderDO order, List orderItems) { + // 1.如果不是拼团订单则结束 + if (!TradeOrderTypeEnum.isCombination(order.getType())) { + return; + } + Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品"); + + // 2. 创建拼团记录 + TradeOrderItemDO item = orderItems.get(0); + KeyValue recordIdAndHeadId = combinationRecordApi.createCombinationRecord( + TradeOrderConvert.INSTANCE.convert(order, item)); + + // 3. 更新拼团相关信息到订单 + // TODO 芋艿,只需要更新 record + orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(), + recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue()); + } + + @Override + public void beforeDeliveryOrder(TradeOrderDO order) { + if (!TradeOrderTypeEnum.isCombination(order.getType())) { + return; + } + // 校验订单拼团是否成功 + if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) { + throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS); + } + } + +} + diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java new file mode 100644 index 0000000000..0f953fec7f --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; +import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 优惠劵的 {@link TradeOrderHandler} 实现类 + * + * @author 芋道源码 + */ +@Component +public class TradeCouponOrderHandler implements TradeOrderHandler { + + @Resource + private CouponApi couponApi; + + @Override + public void afterOrderCreate(TradeOrderDO order, List orderItems) { + if (order.getCouponId() == null || order.getCouponId() <= 0) { + return; + } + // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号 + couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId()) + .setOrderId(order.getId())); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + if (order.getCouponId() == null || order.getCouponId() <= 0) { + return; + } + // 退回优惠劵 + couponApi.returnUsedCoupon(order.getCouponId()); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java new file mode 100644 index 0000000000..0c1f9d497d --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java @@ -0,0 +1,120 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi; +import cn.iocoder.yudao.module.member.api.point.MemberPointApi; +import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum; +import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum; +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue; + +/** + * 会员积分、等级的 {@link TradeOrderHandler} 实现类 + * + * @author owen + */ +@Component +public class TradeMemberPointOrderHandler implements TradeOrderHandler { + + @Resource + private MemberPointApi memberPointApi; + @Resource + private MemberLevelApi memberLevelApi; + + @Resource + private AfterSaleService afterSaleService; + + @Override + public void afterOrderCreate(TradeOrderDO order, List orderItems) { + // 扣减用户积分(订单抵扣)。不在前置扣减的原因,是因为积分扣减时,需要记录关联业务 + reducePoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId()); + } + + @Override + public void afterPayOrder(TradeOrderDO order, List orderItems) { + // 增加用户积分(订单赠送) + addPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, + order.getId()); + + // 增加用户经验 + memberLevelApi.addExperience(order.getUserId(), order.getPayPrice(), + MemberExperienceBizTypeEnum.ORDER_GIVE.getType(), String.valueOf(order.getId())); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } + + // 增加(回滚)用户积分(订单抵扣) + Integer usePoint = getSumValue(orderItems, TradeOrderItemDO::getUsePoint, Integer::sum); + addPoint(order.getUserId(), usePoint, MemberPointBizTypeEnum.ORDER_USE_CANCEL, + order.getId()); + + // 如下的返还,需要经过支持,也就是经历 afterPayOrder 流程 + if (!order.getPayStatus()) { + return; + } + // 扣减(回滚)积分(订单赠送) + Integer givePoint = getSumValue(orderItems, TradeOrderItemDO::getGivePoint, Integer::sum); + reducePoint(order.getUserId(), givePoint, MemberPointBizTypeEnum.ORDER_GIVE_CANCEL, + order.getId()); + // 扣减(回滚)用户经验 + int payPrice = order.getPayPrice() - order.getRefundPrice(); + memberLevelApi.addExperience(order.getUserId(), payPrice, + MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL.getType(), String.valueOf(order.getId())); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { + // 扣减(回滚)积分(订单赠送) + reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM, + orderItem.getId()); + // 增加(回滚)积分(订单抵扣) + addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM, + orderItem.getId()); + + // 扣减(回滚)用户经验 + AfterSaleDO afterSale = afterSaleService.getAfterSale(orderItem.getAfterSaleId()); + memberLevelApi.reduceExperience(order.getUserId(), afterSale.getRefundPrice(), + MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL_ITEM.getType(), String.valueOf(orderItem.getId())); + } + + /** + * 添加用户积分 + *

+ * 目前是支付成功后,就会创建积分记录。 + *

+ * 业内还有两种做法,可以根据自己的业务调整: + * 1. 确认收货后,才创建积分记录 + * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻 + * + * @param userId 用户编号 + * @param point 增加积分数量 + * @param bizType 业务编号 + * @param bizId 业务编号 + */ + protected void addPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) { + if (point != null && point > 0) { + memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId)); + } + } + + protected void reducePoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) { + if (point != null && point > 0) { + memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId)); + } + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java index 646ebd1b75..4cc5c69a37 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.trade.service.order.handler; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; import java.util.List; @@ -35,16 +37,42 @@ public interface TradeOrderHandler { * @param order 订单 * @param orderItems 订单项 */ - default void afterPayOrder(TradeOrderDO order, List orderItems) { - } + default void afterPayOrder(TradeOrderDO order, List orderItems) {} /** - * 订单取消 + * 订单取消后 * * @param order 订单 * @param orderItems 订单项 */ - default void cancelOrder(TradeOrderDO order, List orderItems) { + default void afterCancelOrder(TradeOrderDO order, List orderItems) {} + + /** + * 订单项取消后 + * + * @param order 订单 + * @param orderItem 订单项 + */ + default void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {} + + /** + * 订单发货前 + * + * @param order 订单 + */ + default void beforeDeliveryOrder(TradeOrderDO order) {} + + // ========== 公用方法 ========== + + /** + * 过滤“未售后”的订单项列表 + * + * @param orderItems 订单项列表 + * @return 过滤后的订单项列表 + */ + default List filterOrderItemListByNoneAfterSale(List orderItems) { + return CollectionUtils.filterList(orderItems, + item -> TradeOrderItemAfterSaleStatusEnum.isNone(item.getAfterSaleStatus())); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java new file mode 100644 index 0000000000..d28a643c65 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; +import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +import static java.util.Collections.singletonList; + +/** + * 商品 SKU 库存的 {@link TradeOrderHandler} 实现类 + * + * @author 芋道源码 + */ +@Component +public class TradeProductSkuOrderHandler implements TradeOrderHandler { + + @Resource + private ProductSkuApi productSkuApi; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems)); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { + productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(singletonList(orderItem))); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java deleted file mode 100644 index b9bd3e4bd6..0000000000 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.trade.service.order.handler; - -import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; -import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -/** - * 秒杀订单 handler 实现类 - * - * @author HUIHUI - */ -@Component -public class TradeSeckillHandler implements TradeOrderHandler { - - @Resource - private SeckillActivityApi seckillActivityApi; - - @Override - public void beforeOrderCreate(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isSeckill(order.getType())) { - return; - } - - // 扣减秒杀活动的库存 - seckillActivityApi.updateSeckillStock(order.getSeckillActivityId(), - orderItems.get(0).getSkuId(), orderItems.get(0).getCount()); - } - - @Override - public void cancelOrder(TradeOrderDO order, List orderItems) { - if (TradeOrderTypeEnum.isSeckill(order.getType())) { - return; - } - - } - -} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java new file mode 100644 index 0000000000..68227df7d5 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 秒杀订单的 {@link TradeOrderHandler} 实现类 + * + * @author HUIHUI + */ +@Component +public class TradeSeckillOrderHandler implements TradeOrderHandler { + + @Resource + private SeckillActivityApi seckillActivityApi; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { + if (!TradeOrderTypeEnum.isSeckill(order.getType())) { + return; + } + // 明确校验一下 + Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品"); + + // 扣减秒杀活动的库存 + seckillActivityApi.updateSeckillStockDecr(order.getSeckillActivityId(), + orderItems.get(0).getSkuId(), orderItems.get(0).getCount()); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + if (!TradeOrderTypeEnum.isSeckill(order.getType())) { + return; + } + // 明确校验一下 + Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品"); + + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } + afterCancelOrderItem(order, orderItems.get(0)); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { + if (!TradeOrderTypeEnum.isSeckill(order.getType())) { + return; + } + // 恢复秒杀活动的库存 + seckillActivityApi.updateSeckillStockIncr(order.getSeckillActivityId(), + orderItem.getSkuId(), orderItem.getCount()); + } + +} diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java index 1ddd899cb8..587683797b 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java @@ -28,4 +28,14 @@ public interface MemberLevelApi { */ void addExperience(Long userId, Integer experience, Integer bizType, String bizId); + /** + * 扣减会员经验 + * + * @param userId 会员ID + * @param experience 经验 + * @param bizType 业务类型 {@link MemberExperienceBizTypeEnum} + * @param bizId 业务编号 + */ + void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId); + } diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java index c719ab79f1..3038dba316 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java @@ -20,10 +20,11 @@ public enum MemberExperienceBizTypeEnum { */ ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true), INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true), - ORDER(2, "下单奖励", "下单获得 {} 经验", true), - REFUND(3, "退单扣除", "退单获得 {} 经验", false), SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true), LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true), + ORDER_GIVE(11, "下单奖励", "下单获得 {} 经验", true), + ORDER_GIVE_CANCEL(12, "下单奖励(整单取消)", "取消订单获得 {} 经验", false), // ORDER_GIVE 的取消 + ORDER_GIVE_CANCEL_ITEM(13, "下单奖励(单个退款)", "退款订单获得 {} 经验", false), // ORDER_GIVE 的取消 ; /** diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java index 3450a52aae..ef491f42a7 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java @@ -18,11 +18,14 @@ public enum MemberPointBizTypeEnum implements IntArrayValuable { SIGN(1, "签到", "签到获得 {} 积分", true), ADMIN(2, "管理员修改", "管理员修改 {} 积分", true), - ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分 - ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时,退回积分 - ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时,扣减积分 - AFTER_SALE_REFUND_USED(13, "订单退款", "订单退款,退还 {} 积分", true), // 售后订单成功时,退回积分(对应 ORDER_USE 操作) - AFTER_SALE_DEDUCT_GIVE(14, "订单退款", "订单退款,扣除赠送的 {} 积分", false), // 售后订单成功时,扣减积分(对应 ORDER_GIVE 操作) + + ORDER_USE(11, "订单积分抵扣", "下单使用 {} 积分", false), // 下单时,扣减积分 + ORDER_USE_CANCEL(12, "订单积分抵扣(整单取消)", "订单取消,退还 {} 积分", true), // ORDER_USE 的取消 + ORDER_USE_CANCEL_ITEM(13, "订单积分抵扣(单个退款)", "订单退款,退还 {} 积分", true), // ORDER_USE 的取消 + + ORDER_GIVE(21, "订单积分奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分 + ORDER_GIVE_CANCEL(22, "订单积分奖励(整单取消)", "订单取消,退还 {} 积分", false), // ORDER_GIVE 的取消 + ORDER_GIVE_CANCEL_ITEM(23, "订单积分奖励(单个退款)", "订单退款,扣除赠送的 {} 积分", false) // ORDER_GIVE 的取消 ; /** diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java index 519a8e8a10..79fed98ebd 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java @@ -38,4 +38,9 @@ public class MemberLevelApiImpl implements MemberLevelApi { memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId); } + @Override + public void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId) { + addExperience(userId, -experience, bizType, bizId); + } + }