订单结算页+下单业务

一、订单结算页

订单结算页+下单业务_第1张图片

1.业务分析

(1) 获取用户收货地址信息
一般的收货地址都是多个,使用时选中一个,所以收货地址使用List集合封装

(2)获取购物车商品信息
购物车商品也是多个,使用List集合封装

(3)查询商品库存
查询每个商品是否有库存,显示是否有货,使用Map封装

(4)计算订单金额
使用BigDecimal计算,算出每个商品的总价(价格*件数)然后求和,最后扣除优惠价格得到结算价,还可以做积分业务

(5)页面防重复提交,保证幂等性
重复提交会重复执行下单业务,提交订单前在redis中存一个token令牌,然后在执行下单操作前去redis中去取这个token,如果能取到,代表是第一次提交页面,然后删除令牌,如果取不到证明令牌代表已经被删除了,表示页面不是第一次提交,告诉用户 “请问重复提交页面”
删令牌的操作需要保证原子性,取令牌,校验,删除是三次操作,如果其中一步错误就会导致删除失败,所以必须保证要么全部成功或全部失败,这就是原子性,所以使用redis提供的lua脚本取执行删令牌的操作

2.代码

(1)订单页面实体类设计

public class OrderConfirmVo {

    @Getter @Setter
    /** 会员收获地址列表 **/
    List<MemberAddressVo> memberAddressVos;

    @Getter @Setter
    /** 所有选中的购物项 **/
    List<OrderItemVo> items;

    /** 发票记录 **/
    @Getter @Setter
    /** 优惠券(会员积分) **/
    private Integer integration;

    /** 防止重复提交的令牌 **/
    @Getter @Setter
    private String orderToken;

    private Integer count;
    private BigDecimal total;
    private BigDecimal payPrice;

    @Getter @Setter
    Map<Long,Boolean> stocks;

    /**
     * 获取商品总数量
     */
    public Integer getCount() {
        Integer count = 0;
        if (items != null && items.size() > 0) {
            for (OrderItemVo item : items) {
                count += item.getCount();
            }
        }
        return count;
    }


    /** 订单总额 **/
    //BigDecimal total;
    //计算订单总额
    public BigDecimal getTotal() {
        BigDecimal totalNum = BigDecimal.ZERO;
        for (OrderItemVo item : items) {
            BigDecimal multiply = item.getPrice().multiply(new BigDecimal(Integer.toString(item.getCount())));
            totalNum = totalNum.add(multiply);
        }
        return totalNum;
    }


    /** 应付价格 **/
    //BigDecimal payPrice;
    public BigDecimal getPayPrice() {
        return getTotal();
    }
}

(1)业务使用异步编排提升效率

/**
     * 展示结算页信息
     *
     * Feign 丢失请求头的问题? 如果使用ThreadLocal的方式直接获取id会获取不到,远程调用没经过拦截器获取不到id
     * 异步丢失
     */
    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();
        //TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();


        CompletableFuture<Void> memberRunAsync = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                //每一个线程都来共享之前的请求数据
                RequestContextHolder.setRequestAttributes(requestAttributes);

                //1.查询用户信息
                List<MemberAddressVo> list = memberFeignService.getMemberReceiveAddress(memberOAuthVo.getId());
                orderConfirmVo.setMemberAddressVos(list);
            }
        }, executor);

        CompletableFuture<Void> orderRunAsync = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                //每一个线程都来共享之前的请求数据
                RequestContextHolder.setRequestAttributes(requestAttributes);

                //2.查询商品信息
                List<OrderItemVo> orderItemVos = cartFeignService.orderFeignCart(memberOAuthVo.getId());
                orderConfirmVo.setItems(orderItemVos);
            }
        }, executor).thenRunAsync(new Runnable() {
            @Override
            public void run() {
                //查库存  怎么查库存  查库存参数List skuIds
                List<Long> skuIds = orderConfirmVo.getItems().stream().map(item -> {
                    return item.getSkuId();
                }).collect(Collectors.toList());
                R<List<SkuHasStockVo>> r = wareFeignService.getSkuHasStock(skuIds);
                if (r.getCode() == 0){
                    List<SkuHasStockVo> data = r.getData(new TypeReference<List<SkuHasStockVo>>() {
                    });
                    // 下界 接收参数的类型,上界 返回的类型
                    //Function keyMapper,
                    //Function valueMapper
                    Map<Long, Boolean> collect = data.stream().collect(Collectors.toMap(new Function<SkuHasStockVo, Long>() {
                        @Override
                        public Long apply(SkuHasStockVo skuHasStockVo) {
                            return skuHasStockVo.getSkuId();
                        }
                    }, SkuHasStockVo::getHasStock));
                    orderConfirmVo.setStocks(collect);
                }

            }
        });

        //会员积分
        orderConfirmVo.setIntegration(memberOAuthVo.getIntegration());

        //设置订单token,防止页面重复提交,保证幂等性
        String key = OrderConstant.ORDER_TOKEN_PREFIX + memberOAuthVo.getId();
        String orderToken = UUID.randomUUID().toString().replace("_","");
        redisTemplate.opsForValue().set(key,orderToken) ;

        //set token
        orderConfirmVo.setOrderToken(orderToken);

        CompletableFuture.allOf(memberRunAsync,orderRunAsync).get();
        return orderConfirmVo;
    }

二、下单

1.业务分析

(1)校验页面幂等性
获取token令牌,删除令牌,校验成功进入下单操作,校验失败返回code
(2)创建订单
1.生成订单号,使用唯一ID生成,存入OrderSn
2.获取用户选中的地址信息,存入OrderEntity
3.获取购物车最新的商品信息,这里不能直接获取订单页的商品信息,防止购物车商品信息发生变化与订单页商品不一致
根据上送skuId取获取redis中存储的商品信息,然后筛选出已勾选的商品,并且查出该商品最新价格,最后存入List
(3) 验价
防止商品价格发生变化(可能订单页保存的价格与最新价格存在差异),在下单前进行比对,这里使用两价格相减取绝对值,考虑到存在优惠的问题,只要保证绝对值<0.05就表示验价通过
(4)保存订单数据
验价成功就可以保证订单,但需要添加事务@Transactional
(5)锁定库存
商品下单需要锁定库存,相当于订一批货,那么别人在去买这批货时就无法购买,先占个位置,如果超过支付时间未付款,就解锁库存,锁库存操作是给锁定库存添加需要购买的件数stock_locked + num,最大不能超过商品总库存 stock - stock_locked >= num
锁库存先查该skuId商品在那些仓库有库存,然后循环去锁库存,只要有一个仓库锁库存成功就代表成功,如果没有一个仓库能锁成功代表锁库存失败,告诉用户该商品库存不足,并且锁库存之前需要判断是否有仓库存在库存,没有就直接不用锁库存了

2.代码

(1)下单整体业务

@Transactional
    @Override
    public SubmitOrderResponseVo placeOrder(OrderSubmitVo submitVo) {
        SubmitOrderResponseVo submitOrderResponseVo = new SubmitOrderResponseVo();
        submitOrderResponseVo.setCode(0);
        MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();
        String key = OrderConstant.ORDER_TOKEN_PREFIX + memberOAuthVo.getId();
        //原子性操作:1.取出token 2.对比token 3.删除token
        //execute(RedisScript script, List keys, Object... args)
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long luaReturn = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(key), submitVo.getOrderToken());
        //1-删除成功   0-删除失败
        if (luaReturn.equals(1L)){
            //令牌删除成功 -> 创建订单
            OrderCreateTo order = createOrder(submitVo);

            //验价 应付价格-
            BigDecimal payAmount = order.getOrder().getPayAmount();//应付价格
            BigDecimal payPrice = submitVo.getPayPrice();
            double abs = Math.abs(payAmount.subtract(payPrice).doubleValue());
            if (abs < 0.01){
                //验价成功 -> 保存订单信息 -> 锁定库存

                //TODO 保存订单信息
                saveOrder(order.getOrder(),order.getOrderItems());

                //远程锁定库存
                List<OrderItemEntity> orderItems = order.getOrderItems();
                List<OrderItemVo> collect = orderItems.stream().map(item -> {
                    OrderItemVo orderItemVo = new OrderItemVo();
                    orderItemVo.setSkuId(item.getSkuId());
                    orderItemVo.setCount(item.getSkuQuantity());
                    return orderItemVo;
                }).collect(Collectors.toList());
                OrderStockRequest orderStockRequest = new OrderStockRequest();
                orderStockRequest.setOrderSn(order.getOrder().getOrderSn());
                orderStockRequest.setItemVos(collect);
                //TODO 远程锁库存
                R r = wareFeignService.orderStock(orderStockRequest);
                if (r.getCode() == 0){
                    submitOrderResponseVo.setCode(0);
                    submitOrderResponseVo.setOrder(order.getOrder());
                    return submitOrderResponseVo;
                }else {
                    //锁库存失败
                    //锁定失败
                    String msg = (String) r.get("msg");
                    throw new SkuNoStockException(msg);
                }

            }else {
                //验价失败
                submitOrderResponseVo.setCode(2);
                return submitOrderResponseVo;
            }

        }else {
            //令牌删除失败
            submitOrderResponseVo.setCode(1);
            return submitOrderResponseVo;
        }

    }

(2)获取订单页信息

/**
     *  创建订单
     *     (1)用户信息:地址 1
     *     (2)购物车商品信息:应该直接查询购物车数据,不能获取结算页的商品数据,  list
     *     (3)下单金额  1
     *      (4) 验价
     */
    public OrderCreateTo createOrder(OrderSubmitVo submitVo){
        OrderCreateTo orderCreateTo = new OrderCreateTo();
        //订单号-唯一id
        String timeId = IdWorker.getTimeId();
        OrderEntity orderEntity = buildOrderEntity(submitVo, timeId);
        List<OrderItemEntity> orderItemEntityList = buildOrderItems(timeId);
        //封装:订单总额、应付总额
        OrderEntity order = computePrice(orderEntity,orderItemEntityList);
        orderCreateTo.setOrder(order);
        orderCreateTo.setOrderItems(orderItemEntityList);
        return orderCreateTo;
    }

    /**
     * 封装OrderEntity:
     * 订单总额、应付总额
     * 促销优化金额、积分抵扣金额、优惠券抵扣金额
     *
     */
    public OrderEntity computePrice(OrderEntity order,List<OrderItemEntity> orderItems){
        //订单总额
        BigDecimal totalAmount = new BigDecimal("0.0");
        //促销优化金额、积分抵扣金额、优惠券抵扣金额
        BigDecimal promotionAmount = new BigDecimal("0.0");
        BigDecimal integrationAmount = new BigDecimal("0.0");
        BigDecimal couponAmount = new BigDecimal("0.0");
        //积分、成长值
        Integer integration = 0;
        Integer growth = 0;
        for (OrderItemEntity orderItem : orderItems) {
            totalAmount = totalAmount.add(orderItem.getRealAmount());
            promotionAmount = promotionAmount.add(orderItem.getPromotionAmount());
            integrationAmount = integrationAmount.add(orderItem.getIntegrationAmount());
            couponAmount = couponAmount.add(orderItem.getCouponAmount());
            integration += orderItem.getGiftIntegration();
            growth += orderItem.getGiftGrowth();
        }

        //订单总额
        order.setTotalAmount(totalAmount);
        //应付总额 = 订单总额 + 运费
        order.setPayAmount(totalAmount.add(order.getFreightAmount()));
        //促销优化金额、积分抵扣金额、优惠券抵扣金额
        order.setPromotionAmount(promotionAmount);
        order.setIntegrationAmount(integrationAmount);
        order.setCouponAmount(couponAmount);
        //积分、成长值
        order.setIntegration(integration);
        order.setGrowth(growth);

        return order;
    }

    /**
     * 订单信息
     * OrderEntity
     *
     * 1.订单号
     * 2.用户地址信息
     * 3.运费金额
     */
    public OrderEntity buildOrderEntity(OrderSubmitVo submitVo,String timeId){
        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setOrderSn(timeId);
        orderEntity.setCreateTime(new Date());

        //用户地址
        MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();
        orderEntity.setMemberId(memberOAuthVo.getId());
        MemberAddressVo member = memberFeignService.getMember(memberOAuthVo.getId());
        orderEntity.setReceiverName(member.getName());
        orderEntity.setReceiverPhone(member.getPhone());
        orderEntity.setReceiverPostCode(member.getPostCode());
        orderEntity.setReceiverProvince(member.getProvince());
        orderEntity.setReceiverCity(member.getCity());
        orderEntity.setReceiverRegion(member.getRegion());
        orderEntity.setReceiverDetailAddress(member.getDetailAddress());

        //用户名
        orderEntity.setMemberUsername(memberOAuthVo.getUsername());

        //运费金额
        R r = wareFeignService.fare(submitVo.getAddrId());
        if (r.getCode() == 0){
            FareVo data = (FareVo)r.getData(new TypeReference<FareVo>() {
            });
            //运费金额
            orderEntity.setFreightAmount(data.getFare());
        }
        orderEntity.setPayType(submitVo.getPayType());

        //订单状态-0待付款
        orderEntity.setStatus(OrderConstant.ORDER_STATUS);

        //字段确认天数
        orderEntity.setAutoConfirmDay(7);

        return orderEntity;
    }

    /**
     * 订单商品
     * List  因为商品有多个,所以是list
     *
     * 1.订单号
     * 2.sku信息
     * 3.spu信息
     */
    public List<OrderItemEntity> buildOrderItems(String timeId){
        //查询redis商品信息
        MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();
        List<OrderItemVo> orderItemVos = cartFeignService.orderFeignCart(memberOAuthVo.getId());
        List<OrderItemEntity> collect = orderItemVos.stream().map(item -> {
            OrderItemEntity orderItemEntity = new OrderItemEntity();
            //订单号
            orderItemEntity.setOrderSn(timeId);
            //spu信息
            SpuInfoEntity spuInfo = productFeignService.getSpuBySkuId(item.getSkuId());
            orderItemEntity.setSpuId(spuInfo.getId());
            orderItemEntity.setSpuName(spuInfo.getSpuName());
            orderItemEntity.setCategoryId(spuInfo.getCatalogId());
            //Sku信息
            orderItemEntity.setSkuId(item.getSkuId());
            orderItemEntity.setSkuName(item.getTitle());
            orderItemEntity.setSkuPic(item.getImage());
            orderItemEntity.setSkuPrice(item.getPrice());
            orderItemEntity.setSkuQuantity(item.getCount());
            //商品销售属性组合 List -> String
            //集合 根据指定的分割符转换 为字符串
            String jsonString = JSON.toJSONString(item.getSkuAttrValues());
            orderItemEntity.setSkuAttrsVals(jsonString);
            //促销、优惠券、积分
            orderItemEntity.setPromotionAmount(BigDecimal.ZERO);
            orderItemEntity.setCouponAmount(BigDecimal.ZERO);
            orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);
            //该商品经过优惠后的分解金额
            BigDecimal totalPrice = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
            BigDecimal realPrice = totalPrice.subtract(orderItemEntity.getPromotionAmount())
                            .subtract(orderItemEntity.getCouponAmount())
                                    .subtract(orderItemEntity.getIntegrationAmount());
            orderItemEntity.setRealAmount(realPrice);
            //赠送积分、成长值
            orderItemEntity.setGiftIntegration(totalPrice.intValue());
            orderItemEntity.setGiftGrowth(totalPrice.intValue());

            return orderItemEntity;
        }).collect(Collectors.toList());
        return collect;
    }

(3)锁定库存

    /**
     * 库存锁定
     *
     * 1.查询该商品在那些仓库有库存
     * 2.锁定库存,遍历仓库去锁定库存,只要有一个仓库锁定代表成功,如果没有一个仓库能锁成功,抛异常,该sku商品库存不足
     *
     */
//    @Transactional(rollbackFor = NoWareStockException.class)
    @Transactional
    @Override
    public boolean orderStock(OrderStockRequest orderStockRequest) {
        List<OrderItemVo> itemVos = orderStockRequest.getItemVos();
        List<SkuStockfromWare> collect = itemVos.stream().map(item -> {
            SkuStockfromWare skuStockfromWare = new SkuStockfromWare();
            skuStockfromWare.setSkuId(item.getSkuId());
            skuStockfromWare.setNum(item.getCount());
            //查询该商品在那些仓库有库存
            List<Long> wareId = wareSkuDao.skuStockfromWare(item.getSkuId());
            skuStockfromWare.setWareId(wareId);
            return skuStockfromWare;
        }).collect(Collectors.toList());

        //根据skuId遍历
        for (SkuStockfromWare skuStockfromWare : collect) {
            //判断是否锁定成功
            boolean flag = false;

            //判断该商品是否有仓库存在库存
            List<Long> wareIdList = skuStockfromWare.getWareId();
            if (wareIdList.size() < 0 || wareIdList == null){
                throw new NoWareStockException(skuStockfromWare.getSkuId());
            }
            for (Long wareId : wareIdList) {
                Long count = wareSkuDao.LockedStockFromWare(skuStockfromWare.getSkuId(),wareId,skuStockfromWare.getNum());
                if (count.equals(1L)){
                    //锁定成功
                    flag = true;
                    //该商品锁定库存成功就执行下一个商品
                    break;
                }

            }

            //如果没有一个仓库扣成功,代表此skuId的库存不足
            if (!flag){
                throw new SkuNoStockException(skuStockfromWare.getSkuId());
            }

        }
        return true;
    }

你可能感兴趣的:(java)