Commit 20f5834d9a284353ce72271cc7453f60d899fd6e
1 parent
a1e9bedf
trade:价格计算时,接入优惠劵逻辑
Showing
18 changed files
with
191 additions
and
102 deletions
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApi.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.api.seckill; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; | |
| 4 | - | |
| 5 | -import java.util.Collection; | |
| 6 | -import java.util.List; | |
| 3 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 7 | 4 | |
| 8 | 5 | /** |
| 9 | 6 | * 秒杀活动 API 接口 |
| ... | ... | @@ -22,12 +19,14 @@ public interface SeckillActivityApi { |
| 22 | 19 | void updateSeckillStock(Long id, Long skuId, Integer count); |
| 23 | 20 | |
| 24 | 21 | /** |
| 25 | - * 获取秒杀活动商品信息 | |
| 22 | + * 校验是否参与秒杀商品 | |
| 23 | + * | |
| 24 | + * 如果校验失败,则抛出业务异常 | |
| 26 | 25 | * |
| 27 | - * @param id 活动编号 | |
| 28 | - * @param skuIds sku 编号 | |
| 29 | - * @return 秒杀活动商品信息列表 | |
| 26 | + * @param activityId 活动编号 | |
| 27 | + * @param skuId SKU 编号 | |
| 28 | + * @param count 数量 | |
| 30 | 29 | */ |
| 31 | - List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds); | |
| 30 | + SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); | |
| 32 | 31 | |
| 33 | 32 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillActivityProductRespDTO.java deleted
| 1 | -package cn.iocoder.yudao.module.promotion.api.seckill.dto; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | -import lombok.Data; | |
| 5 | - | |
| 6 | -import java.time.LocalDateTime; | |
| 7 | -import java.util.List; | |
| 8 | - | |
| 9 | -/** | |
| 10 | - * 秒杀活动商品 Response DTO | |
| 11 | - * | |
| 12 | - * @author HUIHUI | |
| 13 | - */ | |
| 14 | -@Data | |
| 15 | -public class SeckillActivityProductRespDTO { | |
| 16 | - | |
| 17 | - /** | |
| 18 | - * 秒杀参与商品编号 | |
| 19 | - */ | |
| 20 | - private Long id; | |
| 21 | - /** | |
| 22 | - * 秒杀活动 id | |
| 23 | - * | |
| 24 | - * 关联 SeckillActivityDO#getId() | |
| 25 | - */ | |
| 26 | - private Long activityId; | |
| 27 | - /** | |
| 28 | - * 秒杀时段 id | |
| 29 | - * | |
| 30 | - * 关联 SeckillConfigDO#getId() | |
| 31 | - */ | |
| 32 | - private List<Long> configIds; | |
| 33 | - /** | |
| 34 | - * 商品 SPU 编号 | |
| 35 | - */ | |
| 36 | - private Long spuId; | |
| 37 | - /** | |
| 38 | - * 商品 SKU 编号 | |
| 39 | - */ | |
| 40 | - private Long skuId; | |
| 41 | - /** | |
| 42 | - * 秒杀金额,单位:分 | |
| 43 | - */ | |
| 44 | - private Integer seckillPrice; | |
| 45 | - /** | |
| 46 | - * 秒杀库存 | |
| 47 | - */ | |
| 48 | - private Integer stock; | |
| 49 | - | |
| 50 | - /** | |
| 51 | - * 秒杀商品状态 | |
| 52 | - * | |
| 53 | - * 枚举 {@link CommonStatusEnum 对应的类} | |
| 54 | - */ | |
| 55 | - private Integer activityStatus; | |
| 56 | - /** | |
| 57 | - * 活动开始时间点 | |
| 58 | - */ | |
| 59 | - private LocalDateTime activityStartTime; | |
| 60 | - /** | |
| 61 | - * 活动结束时间点 | |
| 62 | - */ | |
| 63 | - private LocalDateTime activityEndTime; | |
| 64 | - | |
| 65 | -} |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/dto/SeckillValidateJoinRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.promotion.api.seckill.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 校验参与秒杀 Response DTO | |
| 7 | + */ | |
| 8 | +@Data | |
| 9 | +public class SeckillValidateJoinRespDTO { | |
| 10 | + | |
| 11 | + /** | |
| 12 | + * 秒杀活动名称 | |
| 13 | + */ | |
| 14 | + private String name; | |
| 15 | + /** | |
| 16 | + * 总限购数量 | |
| 17 | + * | |
| 18 | + * 目的:目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用 | |
| 19 | + */ | |
| 20 | + private Integer totalLimitCount; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 秒杀金额 | |
| 24 | + */ | |
| 25 | + private Integer seckillPrice; | |
| 26 | + | |
| 27 | +} | ... | ... |
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
| ... | ... | @@ -56,6 +56,10 @@ public interface ErrorCodeConstants { |
| 56 | 56 | ErrorCode SECKILL_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_008_004, "秒杀活动未关闭或未结束,不能删除"); |
| 57 | 57 | ErrorCode SECKILL_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_008_005, "秒杀活动已关闭,不能重复关闭"); |
| 58 | 58 | ErrorCode SECKILL_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_008_006, "秒杀失败,原因秒杀库存不足"); |
| 59 | + ErrorCode SECKILL_JOIN_ACTIVITY_TIME_ERROR = new ErrorCode(1_013_008_007, "秒杀失败,原因:不在活动时间范围内"); | |
| 60 | + ErrorCode SECKILL_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_008_008, "秒杀失败,原因:秒杀活动已关闭"); | |
| 61 | + ErrorCode SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_008_009, "秒杀失败,原因:单次限购超出"); | |
| 62 | + ErrorCode SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_008_010, "秒杀失败,原因:商品不存在"); | |
| 59 | 63 | |
| 60 | 64 | // ========== 秒杀时段 1-013-009-000 ========== |
| 61 | 65 | ErrorCode SECKILL_CONFIG_NOT_EXISTS = new ErrorCode(1_013_009_000, "秒杀时段不存在"); | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.api.seckill; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; | |
| 4 | -import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; | |
| 3 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 5 | 4 | import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; |
| 6 | 5 | import org.springframework.stereotype.Service; |
| 7 | 6 | |
| 8 | 7 | import javax.annotation.Resource; |
| 9 | -import java.util.Collection; | |
| 10 | -import java.util.List; | |
| 11 | 8 | |
| 12 | 9 | /** |
| 13 | 10 | * 秒杀活动接口 Api 接口实现类 |
| ... | ... | @@ -26,8 +23,8 @@ public class SeckillActivityApiImpl implements SeckillActivityApi { |
| 26 | 23 | } |
| 27 | 24 | |
| 28 | 25 | @Override |
| 29 | - public List<SeckillActivityProductRespDTO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) { | |
| 30 | - return SeckillActivityConvert.INSTANCE.convertList4(activityService.getSeckillActivityProductList(id, skuIds)); | |
| 26 | + public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) { | |
| 27 | + return activityService.validateJoinSeckill(activityId, skuId, count); | |
| 31 | 28 | } |
| 32 | 29 | |
| 33 | 30 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/seckillactivity/SeckillActivityConvert.java
| ... | ... | @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 6 | 6 | import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; |
| 7 | 7 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.product.enums.DictTypeConstants; |
| 9 | -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; | |
| 9 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 10 | 10 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 11 | 11 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityDetailRespVO; |
| 12 | 12 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityRespVO; |
| ... | ... | @@ -140,6 +140,6 @@ public interface SeckillActivityConvert { |
| 140 | 140 | .setStartTime(startTime).setEndTime(endTime); |
| 141 | 141 | } |
| 142 | 142 | |
| 143 | - List<SeckillActivityProductRespDTO> convertList4(List<SeckillProductDO> seckillActivityProductList); | |
| 143 | + SeckillValidateJoinRespDTO convert02(SeckillActivityDO activity, SeckillProductDO product); | |
| 144 | 144 | |
| 145 | 145 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java
| ... | ... | @@ -46,7 +46,7 @@ public interface SeckillActivityMapper extends BaseMapperX<SeckillActivityDO> { |
| 46 | 46 | .eq(SeckillActivityDO::getId, id) |
| 47 | 47 | .gt(SeckillActivityDO::getTotalStock, 0) |
| 48 | 48 | .setSql("stock = stock + " + count) |
| 49 | - .setSql("totalStock = totalStock - " + count)); | |
| 49 | + .setSql("total_stock = total_stock - " + count)); | |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | 52 | default PageResult<SeckillActivityDO> selectPage(AppSeckillActivityPageReqVO pageReqVO, Integer status) { | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
| 1 | 1 | package cn.iocoder.yudao.module.promotion.service.seckill; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 4 | 5 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; |
| ... | ... | @@ -115,4 +116,15 @@ public interface SeckillActivityService { |
| 115 | 116 | */ |
| 116 | 117 | List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds); |
| 117 | 118 | |
| 119 | + /** | |
| 120 | + * 校验是否参与秒杀商品 | |
| 121 | + * | |
| 122 | + * 如果校验失败,则抛出业务异常 | |
| 123 | + * | |
| 124 | + * @param activityId 活动编号 | |
| 125 | + * @param skuId SKU 编号 | |
| 126 | + * @param count 数量 | |
| 127 | + */ | |
| 128 | + SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count); | |
| 129 | + | |
| 118 | 130 | } | ... | ... |
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
| ... | ... | @@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollectionUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjectUtil; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | |
| 7 | 8 | import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; |
| 8 | 9 | import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; |
| 9 | 10 | import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; |
| 10 | 11 | import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; |
| 12 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 11 | 13 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO; |
| 12 | 14 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; |
| 13 | 15 | import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityUpdateReqVO; |
| ... | ... | @@ -15,6 +17,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.product.Sec |
| 15 | 17 | import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; |
| 16 | 18 | import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert; |
| 17 | 19 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; |
| 20 | +import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillConfigDO; | |
| 18 | 21 | import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO; |
| 19 | 22 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillActivityMapper; |
| 20 | 23 | import cn.iocoder.yudao.module.promotion.dal.mysql.seckill.seckillactivity.SeckillProductMapper; |
| ... | ... | @@ -277,8 +280,7 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 277 | 280 | |
| 278 | 281 | @Override |
| 279 | 282 | public List<SeckillProductDO> getSeckillActivityProductList(Long id, Collection<Long> skuIds) { |
| 280 | - // 1、校验秒杀活动是否存在 | |
| 281 | - validateSeckillActivityExists(id); | |
| 283 | + | |
| 282 | 284 | // 2、校验活动商品是否存在 |
| 283 | 285 | List<SeckillProductDO> productList = filterList(seckillProductMapper.selectListByActivityId(id), |
| 284 | 286 | item -> skuIds.contains(item.getSkuId())); |
| ... | ... | @@ -288,4 +290,36 @@ public class SeckillActivityServiceImpl implements SeckillActivityService { |
| 288 | 290 | return productList; |
| 289 | 291 | } |
| 290 | 292 | |
| 293 | + @Override | |
| 294 | + public SeckillValidateJoinRespDTO validateJoinSeckill(Long activityId, Long skuId, Integer count) { | |
| 295 | + // 1.1 校验秒杀活动是否存在 | |
| 296 | + SeckillActivityDO activity = validateSeckillActivityExists(activityId); | |
| 297 | + if (ObjectUtil.notEqual(activity.getStatus(), CommonStatusEnum.ENABLE.getStatus())) { | |
| 298 | + throw exception(SECKILL_JOIN_ACTIVITY_STATUS_CLOSED); | |
| 299 | + } | |
| 300 | + // 1.2 是否在活动时间范围内 | |
| 301 | + if (!LocalDateTimeUtils.isBetween(activity.getStartTime(), activity.getEndTime())) { | |
| 302 | + throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR); | |
| 303 | + } | |
| 304 | + SeckillConfigDO config = seckillConfigService.getCurrentSeckillConfig(); | |
| 305 | + if (config == null || !CollectionUtil.contains(activity.getConfigIds(), config.getId())) { | |
| 306 | + throw exception(SECKILL_JOIN_ACTIVITY_TIME_ERROR); | |
| 307 | + } | |
| 308 | + // 1.3 超过单次购买限制 | |
| 309 | + if (count > activity.getSingleLimitCount()) { | |
| 310 | + throw exception(SECKILL_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED); | |
| 311 | + } | |
| 312 | + | |
| 313 | + // 2.1 校验秒杀商品是否存在 | |
| 314 | + SeckillProductDO product = seckillProductMapper.selectByActivityIdAndSkuId(activityId, skuId); | |
| 315 | + if (product == null) { | |
| 316 | + throw exception(SECKILL_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS); | |
| 317 | + } | |
| 318 | + // 2.2 校验库存是否充足 | |
| 319 | + if (count > product.getStock()) { | |
| 320 | + throw exception(SECKILL_ACTIVITY_UPDATE_STOCK_FAIL); | |
| 321 | + } | |
| 322 | + return SeckillActivityConvert.INSTANCE.convert02(activity, product); | |
| 323 | + } | |
| 324 | + | |
| 291 | 325 | } | ... | ... |
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
| ... | ... | @@ -55,6 +55,7 @@ public interface ErrorCodeConstants { |
| 55 | 55 | ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0"); |
| 56 | 56 | ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_002, "计算快递运费异常,找不到对应的运费模板"); |
| 57 | 57 | ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); |
| 58 | + ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量"); | |
| 58 | 59 | |
| 59 | 60 | // ========== 物流 Express 模块 1-011-004-000 ========== |
| 60 | 61 | ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
| ... | ... | @@ -14,7 +14,7 @@ import java.util.List; |
| 14 | 14 | public class AppTradeOrderSettlementRespVO { |
| 15 | 15 | |
| 16 | 16 | @Schema(description = "交易类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // 对应 TradeOrderTypeEnum 枚举 |
| 17 | - private Integer type = 1; // TODO 芋艿:改成计算 | |
| 17 | + private Integer type; | |
| 18 | 18 | |
| 19 | 19 | @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED) |
| 20 | 20 | private List<Item> items; |
| ... | ... | @@ -75,6 +75,9 @@ public class AppTradeOrderSettlementRespVO { |
| 75 | 75 | @Schema(description = "商品原价(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "500") |
| 76 | 76 | private Integer totalPrice; |
| 77 | 77 | |
| 78 | + @Schema(description = "订单优惠(总),单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") | |
| 79 | + private Integer discountPrice; | |
| 80 | + | |
| 78 | 81 | @Schema(description = "运费金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") |
| 79 | 82 | private Integer deliveryPrice; |
| 80 | 83 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
| ... | ... | @@ -211,7 +211,8 @@ public interface TradeOrderConvert { |
| 211 | 211 | .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus()) |
| 212 | 212 | .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId()) |
| 213 | 213 | .setPickUpStoreId(settlementReqVO.getPickUpStoreId()) |
| 214 | - .setItems(new ArrayList<>(settlementReqVO.getItems().size())); | |
| 214 | + .setItems(new ArrayList<>(settlementReqVO.getItems().size())) | |
| 215 | + .setSeckillActivityId(settlementReqVO.getSeckillActivityId()); | |
| 215 | 216 | // 商品项的构建 |
| 216 | 217 | Map<Long, CartDO> cartMap = convertMap(cartList, CartDO::getId); |
| 217 | 218 | for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) { | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/order/TradeOrderDO.java
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.dal.mysql.order; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.map.MapUtil; | |
| 3 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 7 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 8 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 6 | 9 | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| 7 | 10 | import org.apache.ibatis.annotations.Mapper; |
| 11 | +import org.apache.ibatis.annotations.Param; | |
| 8 | 12 | |
| 9 | 13 | import java.util.Collection; |
| 10 | 14 | import java.util.List; |
| 15 | +import java.util.Map; | |
| 16 | +import java.util.Set; | |
| 11 | 17 | |
| 12 | 18 | @Mapper |
| 13 | 19 | public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| ... | ... | @@ -38,4 +44,13 @@ public interface TradeOrderItemMapper extends BaseMapperX<TradeOrderItemDO> { |
| 38 | 44 | .eq(TradeOrderItemDO::getCommentStatus, commentStatus)); |
| 39 | 45 | } |
| 40 | 46 | |
| 47 | + default int selectProductSumByOrderId(@Param("orderIds") Set<Long> orderIds) { | |
| 48 | + // SQL sum 查询 | |
| 49 | + List<Map<String, Object>> result = selectMaps(new QueryWrapper<TradeOrderItemDO>() | |
| 50 | + .select("SUM(count) AS sumCount") | |
| 51 | + .in("order_id", orderIds)); // 只计算选中的 | |
| 52 | + // 获得数量 | |
| 53 | + return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0; | |
| 54 | + } | |
| 55 | + | |
| 41 | 56 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java
| ... | ... | @@ -82,4 +82,10 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> { |
| 82 | 82 | .eq(TradeOrderDO::getCommentStatus, commentStatus)); |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | + default List<TradeOrderDO> selectListByUserIdAndSeckillActivityId(Long userId, Long seckillActivityId) { | |
| 86 | + return selectList(new LambdaUpdateWrapper<>(TradeOrderDO.class) | |
| 87 | + .eq(TradeOrderDO::getUserId, userId) | |
| 88 | + .eq(TradeOrderDO::getSeckillActivityId, seckillActivityId)); | |
| 89 | + } | |
| 90 | + | |
| 85 | 91 | } | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java
| ... | ... | @@ -82,6 +82,15 @@ public interface TradeOrderQueryService { |
| 82 | 82 | */ |
| 83 | 83 | List<ExpressTrackRespDTO> getExpressTrackList(Long id); |
| 84 | 84 | |
| 85 | + /** | |
| 86 | + * 【会员】在指定秒杀活动下,用户购买的商品数量 | |
| 87 | + * | |
| 88 | + * @param userId 用户编号 | |
| 89 | + * @param activityId 活动编号 | |
| 90 | + * @return 秒杀商品数量 | |
| 91 | + */ | |
| 92 | + int getSeckillProductCount(Long userId, Long activityId); | |
| 93 | + | |
| 85 | 94 | // =================== Order Item =================== |
| 86 | 95 | |
| 87 | 96 | /** | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java
| ... | ... | @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; |
| 13 | 13 | import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; |
| 14 | 14 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; |
| 15 | 15 | import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; |
| 16 | +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; | |
| 16 | 17 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.ExpressClientFactory; |
| 17 | 18 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; |
| 18 | 19 | import cn.iocoder.yudao.module.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; |
| ... | ... | @@ -120,6 +121,18 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { |
| 120 | 121 | return getExpressTrackList(order); |
| 121 | 122 | } |
| 122 | 123 | |
| 124 | + @Override | |
| 125 | + public int getSeckillProductCount(Long userId, Long activityId) { | |
| 126 | + // 获得订单列表 | |
| 127 | + List<TradeOrderDO> orders = tradeOrderMapper.selectListByUserIdAndSeckillActivityId(userId, activityId); | |
| 128 | + orders.removeIf(order -> TradeOrderStatusEnum.isCanceled(order.getStatus())); // 过滤掉【已取消】的订单 | |
| 129 | + if (CollUtil.isEmpty(orders)) { | |
| 130 | + return 0; | |
| 131 | + } | |
| 132 | + // 获得订单项列表 | |
| 133 | + return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId)); | |
| 134 | + } | |
| 135 | + | |
| 123 | 136 | // TODO @puhui999:可以加个 spring 缓存,30 分钟;主要考虑及时性要求不高,但是每次调用需要钱; |
| 124 | 137 | /** |
| 125 | 138 | * 获得订单的物流轨迹 | ... | ... |
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java
| 1 | 1 | package cn.iocoder.yudao.module.trade.service.price.calculator; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 5 | import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi; |
| 4 | -import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillActivityProductRespDTO; | |
| 6 | +import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; | |
| 5 | 9 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; |
| 6 | 10 | import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; |
| 7 | 11 | import org.springframework.core.annotation.Order; |
| 8 | 12 | import org.springframework.stereotype.Component; |
| 9 | 13 | |
| 10 | 14 | import javax.annotation.Resource; |
| 11 | -import java.util.List; | |
| 12 | -import java.util.Map; | |
| 13 | 15 | |
| 14 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | |
| 15 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 16 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 17 | +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT; | |
| 16 | 18 | |
| 19 | +// TODO huihui:单测需要补充 | |
| 17 | 20 | /** |
| 18 | 21 | * 秒杀活动的 {@link TradePriceCalculator} 实现类 |
| 19 | 22 | * |
| ... | ... | @@ -24,22 +27,45 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. |
| 24 | 27 | public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator { |
| 25 | 28 | |
| 26 | 29 | @Resource |
| 27 | - private SeckillActivityApi activityApi; | |
| 30 | + private SeckillActivityApi seckillActivityApi; | |
| 31 | + | |
| 32 | + @Resource | |
| 33 | + private TradeOrderQueryService tradeOrderQueryService; | |
| 28 | 34 | |
| 29 | 35 | @Override |
| 30 | 36 | public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { |
| 31 | - // 1、判断订单类型和是否具有秒杀活动编号 | |
| 37 | + // 1. 判断订单类型和是否具有秒杀活动编号 | |
| 32 | 38 | if (param.getSeckillActivityId() == null) { |
| 33 | 39 | return; |
| 34 | 40 | } |
| 35 | - // 2、获取秒杀活动商品信息 | |
| 36 | - List<SeckillActivityProductRespDTO> productList = activityApi.getSeckillActivityProductList(param.getSeckillActivityId(), convertSet(param.getItems(), | |
| 37 | - TradePriceCalculateReqBO.Item::getSkuId)); | |
| 38 | - Map<Long, SeckillActivityProductRespDTO> productMap = convertMap(productList, SeckillActivityProductRespDTO::getSkuId); | |
| 39 | - result.getItems().forEach(item -> { | |
| 40 | - SeckillActivityProductRespDTO product = productMap.get(item.getSkuId()); | |
| 41 | - item.setActivityPrice(product.getSeckillPrice()); // 设置活动金额 | |
| 42 | - }); | |
| 41 | + Assert.isTrue(param.getItems().size() == 1, "秒杀时,只允许选择一个商品"); | |
| 42 | + // 2. 校验是否可以参与秒杀 | |
| 43 | + TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0); | |
| 44 | + SeckillValidateJoinRespDTO seckillActivity = validateJoinSeckill( | |
| 45 | + param.getUserId(), param.getSeckillActivityId(), | |
| 46 | + orderItem.getSkuId(), orderItem.getCount()); | |
| 47 | + | |
| 48 | + // 3.1 记录优惠明细 | |
| 49 | + Integer discountPrice = orderItem.getPayPrice() - seckillActivity.getSeckillPrice(); | |
| 50 | + TradePriceCalculatorHelper.addPromotion(result, orderItem, | |
| 51 | + param.getSeckillActivityId(), seckillActivity.getName(), PromotionTypeEnum.SECKILL_ACTIVITY.getType(), | |
| 52 | + StrUtil.format("秒杀活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)), | |
| 53 | + discountPrice); | |
| 54 | + // 3.2 更新 SKU 优惠金额 | |
| 55 | + orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice); | |
| 56 | + TradePriceCalculatorHelper.recountPayPrice(orderItem); | |
| 57 | + TradePriceCalculatorHelper.recountAllPrice(result); | |
| 58 | + } | |
| 59 | + | |
| 60 | + private SeckillValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) { | |
| 61 | + // 1. 校验是否可以参与秒杀 | |
| 62 | + SeckillValidateJoinRespDTO seckillActivity = seckillActivityApi.validateJoinSeckill(activityId, skuId, count); | |
| 63 | + // 2. 校验总限购数量,目前只有 trade 有具体下单的数据,需要交给 trade 价格计算使用 | |
| 64 | + int seckillProductCount = tradeOrderQueryService.getSeckillProductCount(userId, activityId); | |
| 65 | + if (seckillProductCount + count > seckillActivity.getTotalLimitCount()) { | |
| 66 | + throw exception(PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT); | |
| 67 | + } | |
| 68 | + return seckillActivity; | |
| 43 | 69 | } |
| 44 | 70 | |
| 45 | 71 | } | ... | ... |