实现带优惠活动和优惠券的购物车列表(尚上优选)

整体概要

        在实现购物车选中商品结算页面时,会查询购物车中的商品所能使用的优惠活动和优惠券(前提是当前用户要具有此优惠券),并自动进行额度减免计算,默认为最优减免选择,此文详细描述如何实现此业务逻辑: 

前置信息说明

 现在有一个实体类 CartInfo 描述的是购物车中每个商品的详细信息

@Data
@ApiModel(description = "CartInfo")
public class CartInfo extends BaseEntity {

	private static final long serialVersionUID = 1L;

	@ApiModelProperty(value = "用户id")
	private Long userId;

	@ApiModelProperty(value = "分类id")
	private Long categoryId;

	@ApiModelProperty(value = "商品类型:0->普通商品 1->秒杀商品")
	private Integer skuType;

	@ApiModelProperty(value = "是否新人专享:0->否;1->是")
	private Integer isNewPerson;

	@ApiModelProperty(value = "sku名称 (冗余)")
	private String skuName;

	@ApiModelProperty(value = "skuid")
	private Long skuId;

	@ApiModelProperty(value = "放入购物车时价格")
	private BigDecimal cartPrice;

	@ApiModelProperty(value = "数量")
	private Integer skuNum;

	@ApiModelProperty(value = "限购个数")
	private Integer perLimit;

	@ApiModelProperty(value = "图片文件")
	private String imgUrl;

	@ApiModelProperty(value = "isChecked")
	private Integer isChecked;

	@ApiModelProperty(value = "状态")
	private Integer status;

	@ApiModelProperty(value = "秒杀开始时间")
	@JsonFormat(pattern = "HH:mm:ss")
	private Date startTime;

	@ApiModelProperty(value = "秒杀结束时间")
	@JsonFormat(pattern = "HH:mm:ss")
	private Date endTime;

	@ApiModelProperty(value = "仓库")
	private Long wareId;

	@ApiModelProperty(value = "当天已购买个数")
	private Integer currentBuyNum = 0;

}

 加入购物车时我们的实现是将CartInfo列表放入redis中 ,以Hash结构存储,用户id为key 商品id为feild,CartInfo为value,设有过期时间,很好的满足了购物车这一特性

使用到的数据库表

商品表

sku_Info(描述商品必要信息,商品有不同的分类)

 优惠活动:一种商品可以对应多个优惠活动,一个活动可以对应多个优惠规则,(一种商品可以对应多种规则)现实中购物也是如此。

activity_info(优惠活动表如:五一促销 国庆促销等不同活动)

 activity_rule(活动规则表,如满5减2,满多少打几折等优惠规则)

activity_sku (优惠活动和商品之间的对应关系)

优惠券:一个用户可以有多个优惠券,一张优惠券有多种使用范围(一个优惠卷可以全场通用,可以固定分类使用,可以固定商品使用)

coupon_info(优惠券表)

coupon_range(优惠券使用范围)

 

 coupon_use(用户和优惠券之间的对应关系)

详细逻辑分析

    获取购物车中每个购物项参与的活动,一个活动有多种规则,根据活动规则进行分组,因为活动规则才能确定优惠信息,根据分组计算商品参与活动之后的金额是多少,之后获取购物车中可以使用的优惠券列表,根据优惠券进行分组,计算商品使用优惠券之后的金额(一次只能使用一张优惠券)计算没有参与活动,没有使用优惠券的原始金额 ,计算参与活动,使用优惠券之后的金额,封装需要的数据返回。

业务实现细节

特别说明。本文是基于项目的实现,可能代码和数据库中有与本文主要实现无关的部分,存在过度设计,请谅解

 接口调用

说明从当前线程中获取当前用户id,根据当前用户id从redis中获取购物车列表CartList,远程调用方法实现带优惠活动和优惠券的购物车列表,返回数据类型为OrderConfirmVo (本文最终封装目的)

    /**
     * 查询带优惠卷的购物车
     *
     * @return
     */
    @GetMapping("activityCartList")
    public Result activityCartList() {
        // 获取用户Id
        Long userId = AuthContextHolder.getUserId();
        List<CartInfo> cartInfoList = cartInfoService.getCartList(userId);

        OrderConfirmVo orderTradeVo =
                activityFeignClient.findCartActivityAndCoupon(cartInfoList, userId);
        return Result.ok(orderTradeVo);
    }
@Data
public class OrderConfirmVo implements Serializable {
	
	private static final long serialVersionUID = 1L;

	@ApiModelProperty(value = "预生产订单号")
	private String orderNo;

	@ApiModelProperty(value = "用户对应的团长地址")
	private LeaderAddressVo leaderAddressVo;
	
	@ApiModelProperty(value = "购物项列表")
	private List<CartInfoVo> carInfoVoList;

	@ApiModelProperty(value = "订单优惠券列表")
    private List<CouponInfo> couponInfoList;

	@ApiModelProperty(value = "促销优惠金额")
	private BigDecimal activityReduceAmount;

	@ApiModelProperty(value = "优惠券优惠金额")
	private BigDecimal couponReduceAmount;

	@ApiModelProperty(value = "购物车原始总金额")
	private BigDecimal originalTotalAmount;

	@ApiModelProperty(value = "最终总金额")
	private BigDecimal totalAmount;

}

 远程方法定义

    //获取购物车里面满足条件优惠卷和活动的信息
    @PostMapping("inner/findCartActivityAndCoupon/{userId}")
    public OrderConfirmVo findCartActivityAndCoupon(@RequestBody List<CartInfo> cartInfoList,
                                                    @PathVariable("userId") Long userId) {
        return activityInfoService.findCartActivityAndCoupon(cartInfoList,userId);
    }

接口定义

    //获取购物车里面满足条件优惠卷和活动的信息
    OrderConfirmVo findCartActivityAndCoupon(List<CartInfo> cartInfoList, Long userId);

方法实现:

1.先获取购物车数据,查询商品参与的活动,根据活动规则分组,把一种活动规则对应的商品集合封装,再整体封装

2.参加活动之后的减免金额计算

3.获取购物车中可以使用的优惠券列表

4.计算使用优惠券之后可以减免的金额

5.计算原始金额,和优惠之后的金额,封装数据返回

    //获取购物车里面满足条件优惠卷和活动的信息
    @Override
    public OrderConfirmVo findCartActivityAndCoupon(List<CartInfo> cartInfoList,
                                                    Long userId) {
       
        //1 获取购物车,查询每个商品参与活动,根据活动规则分组,
        //一个活动规则对应多个商品
        //cartInfoVoList封装一个规则对应的购物项信息,并封装成集合
        List<CartInfoVo> cartInfoVoList = this.findCartActivityList(cartInfoList);

        //2 计算参与活动之后优惠的金额
        BigDecimal activityReduceAmount = cartInfoVoList
                .stream()
                .filter(cartInfoVo -> cartInfoVo.getActivityRule() != null)
                .map(cartInfoVo -> cartInfoVo.getActivityRule().getReduceAmount())
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        //3 获取购物车中的商品可以使用优惠卷列表
        List<CouponInfo> couponInfoList =
                couponInfoService.findCartCouponInfo(cartInfoList,userId);
        
        //4 计算商品使用优惠卷之后金额,一次只能使用一张优惠卷
        BigDecimal couponReduceAmount = new BigDecimal(0);
        if(!CollectionUtils.isEmpty(couponInfoList)) {
            //优惠券可以优惠的总金额
            couponReduceAmount = couponInfoList.stream()
                    .filter(couponInfo -> couponInfo.getIsOptimal().intValue() == 1)
                    .map(couponInfo -> couponInfo.getAmount())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        //5 计算没有参与活动,没有使用优惠卷原始金额
        BigDecimal originalTotalAmount = cartInfoList.stream()
                .filter(cartInfo -> cartInfo.getIsChecked() == 1)
                .map(cartInfo -> cartInfo.getCartPrice().multiply(new BigDecimal(cartInfo.getSkuNum())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        //6 最终金额
        BigDecimal totalAmount =
            originalTotalAmount.subtract(activityReduceAmount).subtract(couponReduceAmount);

        //7 封装需要数据到OrderConfirmVo,返回 用于生成订单数据
        OrderConfirmVo orderTradeVo = new OrderConfirmVo();
        orderTradeVo.setCarInfoVoList(cartInfoVoList);
        orderTradeVo.setActivityReduceAmount(activityReduceAmount);
        orderTradeVo.setCouponInfoList(couponInfoList);
        orderTradeVo.setCouponReduceAmount(couponReduceAmount);
        orderTradeVo.setOriginalTotalAmount(originalTotalAmount);
        orderTradeVo.setTotalAmount(totalAmount);   //最终总金额
        return orderTradeVo;
    }

获取购物车对应的活动规则

返回结果类CartInfoVo  用于封装一种活动的规则对应的商品列表有哪些    一个商品对应多个活动,一个活动有多个规则

@Data
public class CartInfoVo implements Serializable {
	
	private static final long serialVersionUID = 1L;

	/**
	 * 购物项凑单,同一活动对应的最优活动规则
	 */
	@ApiModelProperty(value = "cartInfoList")
	private List<CartInfo> cartInfoList;

	@ApiModelProperty(value = "活动规则")
	private ActivityRule activityRule;


}
获取购物车对应规则数据
@Override
    public List<CartInfoVo> findCartActivityList(List<CartInfo> cartInfoList) {
        //创建最终返回集合
        List<CartInfoVo> cartInfoVoList = new ArrayList<>();
        //获取所有skuId
        List<Long> skuIdList = cartInfoList
                .stream()
                .map(CartInfo::getSkuId)
                .collect(Collectors.toList());
        //根据所有skuId列表获取参与活动
        List<ActivitySku> activitySkuList = baseMapper.selectCartActivity(skuIdList);
        //根据活动进行分组,每个活动里面有哪些skuId信息
        //map里面key是分组字段 活动id
        // value是每组里面sku列表数据,set集合
        Map<Long, Set<Long>> activityIdToSkuIdListMap = activitySkuList
                .stream()
                .collect(
                        Collectors.groupingBy(
                                ActivitySku::getActivityId,
                                Collectors.mapping(ActivitySku::getSkuId, Collectors.toSet())
                        )
                );
        //获取活动里面规则数据
        //key是活动id  value是活动里面规则列表数据
        Map<Long,List<ActivityRule>> activityIdToActivityRuleListMap
                = new HashMap<>();
        //所有活动id
        Set<Long> activityIdSet = activitySkuList
                .stream()
                .map(ActivitySku::getActivityId)
                .collect(Collectors.toSet());
        if(!CollectionUtils.isEmpty(activityIdSet)) {
            //activity_rule表
            LambdaQueryWrapper<ActivityRule> wrapper = new LambdaQueryWrapper<>();
            //排序规则
            wrapper.orderByDesc(ActivityRule::getConditionAmount,ActivityRule::getConditionNum);
            //选择条件
            wrapper.in(ActivityRule::getActivityId,activityIdSet);
            //规则数据库列表
            List<ActivityRule> activityRuleList = activityRuleMapper.selectList(wrapper);
            //封装到activityIdToActivityRuleListMap里面
            //根据活动id进行分组(一个活动可以对应多个满减规则)
            activityIdToActivityRuleListMap = activityRuleList
                    .stream()
                    .collect(Collectors.groupingBy(activityRule -> activityRule.getActivityId())
            );
        }

        //有活动的购物项skuId
        Set<Long> activitySkuIdSet = new HashSet<>();
        if(!CollectionUtils.isEmpty(activityIdToSkuIdListMap)) {
            //遍历activityIdToSkuIdListMap集合
            Iterator<Map.Entry<Long, Set<Long>>> iterator
                    = activityIdToSkuIdListMap.entrySet().iterator();
            while(iterator.hasNext()) {
                Map.Entry<Long, Set<Long>> entry = iterator.next();
                //活动id
                Long activityId = entry.getKey();
                //每个活动对应skuId列表
                Set<Long> currentActivitySkuIdSet = entry.getValue();
                //获取当前活动对应的购物项列表
                //参加活动的购物项的列表
                List<CartInfo> currentActivityCartInfoList = cartInfoList
                        .stream()
                        .filter(cartInfo ->
                                currentActivitySkuIdSet.contains(cartInfo.getSkuId()))
                        .collect(Collectors.toList());

                //计数购物项总金额和总数量
                BigDecimal activityTotalAmount =
                        this.computeTotalAmount(currentActivityCartInfoList);
                int activityTotalNum =
                        this.computeCartNum(currentActivityCartInfoList);

                //计算活动对应规则
                //根据activityId获取活动对应规则
                List<ActivityRule> currentActivityRuleList =
                        activityIdToActivityRuleListMap.get(activityId);
                //活动类型
                ActivityType activityType = currentActivityRuleList.get(0).getActivityType();
                //判断活动类型:满减和打折
                ActivityRule activityRule = null;
                if(activityType == ActivityType.FULL_REDUCTION) {//满减"
                    activityRule =
                            this.computeFullReduction(activityTotalAmount,
                                    currentActivityRuleList);
                } else {//满量
                    activityRule
                            = this.computeFullDiscount(activityTotalNum,
                            activityTotalAmount,
                            currentActivityRuleList);
                }

                //CartInfoVo封装
                CartInfoVo cartInfoVo = new CartInfoVo();
                cartInfoVo.setActivityRule(activityRule);
                cartInfoVo.setCartInfoList(currentActivityCartInfoList);
                cartInfoVoList.add(cartInfoVo);

                //记录哪些购物项参与活动
                activitySkuIdSet.addAll(currentActivitySkuIdSet);
            }
        }
        
        //没有活动购物项skuId
        //获取哪些skuId没有参加活动
        skuIdList.removeAll(activitySkuIdSet);
        if(!CollectionUtils.isEmpty(skuIdList)) {
            //skuId对应购物项
            Map<Long, CartInfo> skuIdCartInfoMap = cartInfoList.stream().collect(
                    Collectors.toMap(CartInfo::getSkuId, CartInfo -> CartInfo)
            );
            for(Long skuId  : skuIdList) {
                CartInfoVo cartInfoVo = new CartInfoVo();
                cartInfoVo.setActivityRule(null);//没有活动

                List<CartInfo> cartInfos = new ArrayList<>();
                CartInfo cartInfo = skuIdCartInfoMap.get(skuId);
                cartInfos.add(cartInfo);
                cartInfoVo.setCartInfoList(cartInfos);

                cartInfoVoList.add(cartInfoVo);
            }
        }
        
        return cartInfoVoList;
    }

根据所有skuId列表获取参与活动 

   <!-- //根据所有skuId列表获取参与活动-->
    <select id="selectCartActivity" resultMap="ActivitySkuMap">
        select
            info.id as activityId,
            sku.sku_id as skuId
        from activity_info info
        inner join activity_sku sku on info.id = sku.activity_id
        <where>
            and sku.sku_id in
            <foreach collection="skuIdList" item="item" open="(" close=")" separator=",">
                #{item}
            </foreach>
        </where>
        and now() between info.start_time  and info.end_time
    </select>

 购物车可以使用的优惠卷列表

获取购物车可以使用的优惠券列表

//3 获取购物车可以使用优惠卷列表
    @Override
    public List<CouponInfo> findCartCouponInfo(List<CartInfo> cartInfoList,
                                               Long userId) {
        //1 根据userId获取当前用户全部优惠卷
        //coupon_use  coupon_info
        List<CouponInfo> userAllCouponInfoList = baseMapper.selectCartCouponInfoList(userId);
        if(CollectionUtils.isEmpty(userAllCouponInfoList)) {
            return new ArrayList<CouponInfo>();
        }
        //2 从第一步返回list集合中,获取所有优惠卷id列表
        List<Long> couponIdList = userAllCouponInfoList
                .stream().map(couponInfo -> couponInfo.getId())
                .collect(Collectors.toList());

        //3 查询优惠卷对应的范围  coupon_range
        //couponRangeList
        LambdaQueryWrapper<CouponRange> wrapper = new LambdaQueryWrapper<>();
        // id in (1,2,3)
        wrapper.in(CouponRange::getCouponId,couponIdList);
        List<CouponRange> couponRangeList = couponRangeMapper.selectList(wrapper);

        //4 获取优惠卷id 对应skuId列表(一个优惠券可以多种商品使用)
        //优惠卷id进行分组,得到map集合
        //     Map<Long,List<Long>>
        Map<Long,List<Long>> couponIdToSkuIdMap =
                this.findCouponIdToSkuIdMap(cartInfoList,couponRangeList);

        //5 遍历全部优惠卷集合,判断优惠卷类型
        //全场通用  sku和分类
        BigDecimal reduceAmount = new BigDecimal(0);
        CouponInfo optimalCouponInfo = null;
        for(CouponInfo couponInfo:userAllCouponInfoList) {
            //全场通用
            if(CouponRangeType.ALL == couponInfo.getRangeType()) {
                //全场通用
                //判断是否满足优惠使用门槛
                //计算购物车商品的总价
                BigDecimal totalAmount = computeTotalAmount(cartInfoList);
                if(totalAmount.subtract(couponInfo.getConditionAmount()).doubleValue() >= 0){
                    couponInfo.setIsSelect(1);    //是否选择该优惠券
                }
            } else {
                //优惠卷id获取对应skuId列表
                List<Long> skuIdList
                        = couponIdToSkuIdMap.get(couponInfo.getId());
                //查询满足使用范围购物项
                //(可使用优惠券的购物单集合)
                List<CartInfo> currentCartInfoList = cartInfoList.stream()
                        .filter(cartInfo -> skuIdList.contains(cartInfo.getSkuId()))
                        .collect(Collectors.toList());

                BigDecimal totalAmount = computeTotalAmount(currentCartInfoList);
                if(totalAmount.subtract(couponInfo.getConditionAmount()).doubleValue() >= 0){
                    couponInfo.setIsSelect(1);
                }
            }
            if (couponInfo.getIsSelect().intValue() == 1 &&
                    couponInfo.getAmount().subtract(reduceAmount).doubleValue() > 0) {
                reduceAmount = couponInfo.getAmount();
                optimalCouponInfo = couponInfo;
            }
            //优惠券已经排序。第一个已经是额度最大

        }
        //6 返回List<CouponInfo>
        if(null != optimalCouponInfo) {
            optimalCouponInfo.setIsOptimal(1);
        }
        return userAllCouponInfoList;
    }
获取优惠卷Id 对应购物车中skuId列表(一个优惠券可以多种商品使用)
    //4 获取优惠卷id 对应skuId列表(一个优惠券可以有多种商品使用)
    //优惠卷id进行分组,得到map集合
    private Map<Long, List<Long>> findCouponIdToSkuIdMap(List<CartInfo> cartInfoList,
                                                         List<CouponRange> couponRangeList) {
        Map<Long, List<Long>> couponIdToSkuIdMap = new HashMap<>();
        //couponRangeList数据处理,根据优惠卷id分组
        //Map<CouponId, List<CouponRange>>
        Map<Long, List<CouponRange>> couponRangeToRangeListMap = couponRangeList.stream()
                .collect(
                        Collectors.groupingBy(couponRange -> couponRange.getCouponId())
                );

        //遍历map集合
        Iterator<Map.Entry<Long, List<CouponRange>>> iterator =
                couponRangeToRangeListMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, List<CouponRange>> entry = iterator.next();
            Long couponId = entry.getKey();
            List<CouponRange> rangeList = entry.getValue();

            //创建集合 set
            Set<Long> skuIdSet = new HashSet<>();
            for (CartInfo cartInfo:cartInfoList) {
                for (CouponRange couponRange:rangeList) {
                    //判断
                    if(couponRange.getRangeType() == CouponRangeType.SKU  //商品优惠规则
                       && couponRange.getRangeId().longValue() == cartInfo.getSkuId().longValue()) {
                        skuIdSet.add(cartInfo.getSkuId());
                    } else if(couponRange.getRangeType() == CouponRangeType.CATEGORY   //分类优惠规则
                     && couponRange.getRangeId().longValue() == cartInfo.getCategoryId().longValue()) {
                        //说明此规则属于该商品,可以使用 ,加入集合
                        skuIdSet.add(cartInfo.getSkuId());
                    } else {
                        //全场通用

                    }
                }
            }
            couponIdToSkuIdMap.put(couponId,new ArrayList<>(skuIdSet));
        }
        return couponIdToSkuIdMap;
    }

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值