day19 提交订单跳转支付页

提交订单到支付页功能实现

1.直接上流程图

未命名文件

2.代码实现

  • controller

        /**
         * 下单功能
         *
         * @param vo
         * @return
         */
        @PostMapping(value = "/submitOrder")
        public String submitOrder(OrderSubmitVo vo, Model model, RedirectAttributes attributes) {
            try {
                SubmitOrderResponseVo responseVo = orderService.submitOrder(vo);
                // 下单成功
                if (responseVo.getCode() == 0) {
                    //成功
                    model.addAttribute("submitOrderResp", responseVo);
                    return "pay";
                } else {
                    // 下单失败
                    String msg = "下单失败:";
                    switch (responseVo.getCode()) {
                        case 1: msg += "令牌订单信息过期,请刷新再次提交"; break;
                        case 2: msg += "订单商品价格发生变化,请确认后再次提交"; break;
                        case 3: msg += "库存锁定失败,商品库存不足"; break;
                    }
                    attributes.addFlashAttribute("msg",msg);
                    return "redirect:http://order.dreammall.com/toTrade";
                }
            } catch (Exception e) {
                if (e instanceof NoStockException) {
                    String message = e.getMessage();
                    attributes.addFlashAttribute("msg",message);
                }
                return "redirect:http://order.dreammall.com/toTrade";
            }
        }
    }
    
  • service

        @Override
        @Transactional(rollbackFor = Exception.class)
        public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
            submitVoThreadLocal.set(vo);
            // 获取当前用户登录的信息
            MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
            SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
            responseVo.setCode(0);
            // 1.校验令牌token
            // 如果令牌验证通过,使用lua脚本删除redis中令牌信息,保证原子性
            String scriptStr = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(scriptStr, Long.class);
            Long result = stringRedisTemplate.execute(redisScript,
                    Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId())
                    , vo.getOrderToken());
            // 验证令牌失败
            if (result == 0L) {
                responseVo.setCode(1);
                return responseVo;
            } else {
                // 验证令牌成功
                // 2.创建订单
                // 2.1 创建订单数据
                OrderCreateTo orderCreateTo = createOrder();
                // 3. 验证价格 对比vo里面的价格
                if (Math.abs(vo.getPayPrice().subtract(orderCreateTo.getPayPrice()).doubleValue()) < 0.01) {
                    // 3.1 对比成功,保证订单数据 order、orderItem
                    saveOrder(orderCreateTo);
                    // 4. 从orderItems中取出要锁定的订单
                    WareSkuLockVo wareSkuLockVo = buildWareSkuLockVo(orderCreateTo);
                    // 4.1 远程调用锁定库存
                    R r = wareFeignService.orderLockStock(wareSkuLockVo);
                    // 4.2 锁定成功 返回数据
                    responseVo.setCode(r.getCode() != 0 ? 3 : 0);
                    // 4.3 锁定失败 返回3
                    return responseVo;
                } else {
                    // 3.2 对比失败,返回2
                    responseVo.setCode(2);
                    return responseVo;
                }
            }
        }
    
        /**
         * 构建锁定订单
         *
         * @param orderCreateTo
         */
        private WareSkuLockVo buildWareSkuLockVo(OrderCreateTo orderCreateTo) {
            WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
            List<OrderItemVo> itemVoList = orderCreateTo.getOrderItems().stream().map((item) -> {
                OrderItemVo orderItemVo = new OrderItemVo();
                orderItemVo.setSkuId(item.getSkuId());
                // 购买商品数量
                orderItemVo.setCount(item.getSkuQuantity());
                orderItemVo.setTitle(item.getSkuName());
                return orderItemVo;
            }).collect(Collectors.toList());
            wareSkuLockVo.setOrderSn(orderCreateTo.getOrder().getOrderSn());
            wareSkuLockVo.setLocks(itemVoList);
            return wareSkuLockVo;
        }
    
        /**
         * 保存订单数据
         *
         * @param orderCreateTo
         */
        private void saveOrder(OrderCreateTo orderCreateTo) {
            save(orderCreateTo.getOrder());
            orderItemService.saveBatch(orderCreateTo.getOrderItems());
        }
    
        /**
         * 创建订单数据
         *
         * @return
         */
        private OrderCreateTo createOrder() {
            OrderCreateTo orderCreateTo = new OrderCreateTo();
            // 雪花算法生成订单号
            String orderSn = IdWorker.getTimeId();
            // 设置订单数据
            Order order = buildOrder(orderSn);
            orderCreateTo.setOrder(order);
            // 设置订单项数据
            List<OrderItem> orderItemList = buildOrderItems(orderSn);
            orderCreateTo.setOrderItems(orderItemList);
            // 设置运费
            orderCreateTo.setFare(order.getFreightAmount());
            // 计算价格 给order里面赋值
            computePrice(order, orderItemList);
            // 设置应付价格
            orderCreateTo.setPayPrice(order.getPayAmount());
            return orderCreateTo;
        }
    
        /**
         * 计算价格
         *
         * @param order
         * @param orderItemList
         */
        private void computePrice(Order order, List<OrderItem> orderItemList) {
            //总价
            BigDecimal total = new BigDecimal("0.0");
            //优惠价
            BigDecimal coupon = new BigDecimal("0.0");
            BigDecimal intergration = new BigDecimal("0.0");
            BigDecimal promotion = new BigDecimal("0.0");
    
            //积分、成长值
            Integer integrationTotal = 0;
            Integer growthTotal = 0;
    
            //订单总额,叠加每一个订单项的总额信息
            for (OrderItem orderItem : orderItemList) {
                //优惠价格信息
                coupon = coupon.add(orderItem.getCouponAmount());
                promotion = promotion.add(orderItem.getPromotionAmount());
                intergration = intergration.add(orderItem.getIntegrationAmount());
    
                //总价
                total = total.add(orderItem.getRealAmount());
    
                //积分信息和成长值信息
                integrationTotal += orderItem.getGiftIntegration();
                growthTotal += orderItem.getGiftGrowth();
    
            }
            //1、订单价格相关的
            order.setTotalAmount(total);
            //设置应付总额(总额+运费)
            order.setPayAmount(total.add(order.getFreightAmount()));
            order.setCouponAmount(coupon);
            order.setPromotionAmount(promotion);
            order.setIntegrationAmount(intergration);
    
            //设置积分成长值信息
            order.setIntegration(integrationTotal);
            order.setGrowth(growthTotal);
    
            //设置删除状态(0-未删除,1-已删除)
            order.setDeleteStatus(0);
        }
    
        /**
         * 创建订单项数据
         *
         * @param orderSn
         * @return
         */
        private List<OrderItem> buildOrderItems(String orderSn) {
            // 最后确定每个购物项的价格(最新的价格)
            List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
            return currentCartItems.stream().map(item -> {
                OrderItem orderItem = builderOrderItem(item);
                orderItem.setOrderSn(orderSn);
                return orderItem;
            }).collect(Collectors.toList());
        }
    
        private OrderItem builderOrderItem(OrderItemVo items) {
    
            OrderItem orderItemEntity = new OrderItem();
    
            //1、商品的spu信息
            Long skuId = items.getSkuId();
            //获取spu的信息
            R spuInfo = productFeignService.getSpuInfoBySkuId(skuId);
            SpuInfoVo spuInfoData = spuInfo.getData("data", new TypeReference<SpuInfoVo>() {});
            orderItemEntity.setSpuId(spuInfoData.getId());
            orderItemEntity.setSpuName(spuInfoData.getSpuName());
            orderItemEntity.setSpuBrand(spuInfoData.getBrandName());
            orderItemEntity.setCategoryId(spuInfoData.getCatalogId());
    
            //2、商品的sku信息
            orderItemEntity.setSkuId(skuId);
            orderItemEntity.setSkuName(items.getTitle());
            orderItemEntity.setSkuPic(items.getImage());
            orderItemEntity.setSkuPrice(items.getPrice());
            orderItemEntity.setSkuQuantity(items.getCount());
    
            //使用StringUtils.collectionToDelimitedString将list集合转换为String
            String skuAttrValues = StringUtils.collectionToDelimitedString(items.getSkuAttrValues(), ";");
            orderItemEntity.setSkuAttrsVals(skuAttrValues);
    
            //3、商品的优惠信息
    
            //4、商品的积分信息
            orderItemEntity.setGiftGrowth(items.getPrice().multiply(new BigDecimal(items.getCount())).intValue());
            orderItemEntity.setGiftIntegration(items.getPrice().multiply(new BigDecimal(items.getCount())).intValue());
    
            //5、订单项的价格信息
            orderItemEntity.setPromotionAmount(BigDecimal.ZERO);
            orderItemEntity.setCouponAmount(BigDecimal.ZERO);
            orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);
    
            //当前订单项的实际金额.总额 - 各种优惠价格
            //原来的价格
            BigDecimal origin = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
            //原价减去优惠价得到最终的价格
            BigDecimal subtract = origin.subtract(orderItemEntity.getCouponAmount())
                    .subtract(orderItemEntity.getPromotionAmount())
                    .subtract(orderItemEntity.getIntegrationAmount());
            orderItemEntity.setRealAmount(subtract);
    
            return orderItemEntity;
        }
    
    
        /**
         * 创建订单信息
         *
         * @param orderSn
         * @return
         */
        private Order buildOrder(String orderSn) {
    
            //获取当前用户登录信息
            MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
    
            Order Order = new Order();
            Order.setMemberId(memberResponseVo.getId());
            Order.setOrderSn(orderSn);
            Order.setMemberUsername(memberResponseVo.getUsername());
    
            OrderSubmitVo orderSubmitVo = submitVoThreadLocal.get();
    
            //远程获取收货地址和运费信息
            R fareAddressVo = wareFeignService.getFare(orderSubmitVo.getAddrId());
            FareVo fareResp = fareAddressVo.getData(new TypeReference<FareVo>() {
            });
    
            //获取到运费信息
            BigDecimal fare = fareResp.getFare();
            Order.setFreightAmount(fare);
    
            //获取到收货地址信息
            MemberAddressVo address = fareResp.getAddress();
            //设置收货人信息
            Order.setReceiverName(address.getName());
            Order.setReceiverPhone(address.getPhone());
            Order.setReceiverPostCode(address.getPostCode());
            Order.setReceiverProvince(address.getProvince());
            Order.setReceiverCity(address.getCity());
            Order.setReceiverRegion(address.getRegion());
            Order.setReceiverDetailAddress(address.getDetailAddress());
    
            //设置订单相关的状态信息
            Order.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
            Order.setAutoConfirmDay(7);
            Order.setConfirmStatus(0);
            return Order;
        }
    
  • feign远程调用锁库存

        /**
         * 锁定库存
         * @param wareSkuLockVo
         * @return
         */
        @PostMapping("/lock/order")
        public R orderLockStock(@RequestBody WareSkuLockVo wareSkuLockVo){
            try {
                boolean lockStock = wareSkuService.orderLockStock(wareSkuLockVo);
                return R.ok().setData(lockStock);
            } catch (NoStockException e) {
                return R.error(BizCodeEnum.NO_STOCK_EXCEPTION.getCode(),BizCodeEnum.NO_STOCK_EXCEPTION.getMsg());
            }
        }
    
  • service

        @Override
        @Transactional
        public boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {
    
            List<SkuWareHasStock> collect = wareSkuLockVo.getLocks().stream().map(item -> {
                SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
                skuWareHasStock.setSkuId(item.getSkuId());
                List<Long> wareIdList = baseMapper.listWareIdHasSkuStock(item.getSkuId());
                skuWareHasStock.setWareId(wareIdList);
                skuWareHasStock.setNum(item.getCount());
                return skuWareHasStock;
            }).collect(Collectors.toList());
    
            // 锁定库存
            for (SkuWareHasStock skuWareHasStock : collect) {
                boolean skuStocked = false;
                List<Long> wareIds = skuWareHasStock.getWareId();
                Long skuId = skuWareHasStock.getSkuId();
                // 没有库存信息 直接返回商品没有库存
                if (CollUtil.isEmpty(wareIds)) {
                    throw new NoStockException(skuId);
                }
                // 扣减订单数量,如果当前仓库扣减失败,尝试下一个仓库
                for (Long wareId : wareIds) {
                    Long count = baseMapper.lockSkuStock(skuId, wareId, skuWareHasStock.getNum());
                    if (count == 1) {
                        skuStocked = true;
                        break;
                    }
                }
                // 所有的库存都扣减失败
                if (!skuStocked) {
                    throw new NoStockException(skuId);
                }
            }
            return true;
        }
    
  • sql

       <select id="listWareIdHasSkuStock" resultType="java.lang.Long">
            SELECT ware_id FROM wms_ware_sku WHERE sku_id = #{skuId} and stock > 0
        </select>
        
            <update id="lockSkuStock">
            update wms_ware_sku set stock_locked = stock_locked + #{num} where sku_id = #{skuId} and ware_id = #{wareId} and stock-stock_locked >= #{num}
        </update>
    

3.缺陷

  • 保存订单数据后,redis购物车数据没有删除
  • 库存扣减后,数据没有回滚 订单数据没有被删除
  • for循环中太多远程调用,吞吐量太低
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的万年历面的代码,可以根据用户的选择跳转到不同年月的日历。 HTML代码: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>万年历</title> <style type="text/css"> .calendar { margin: 0 auto; width: 600px; height: 600px; border: 1px solid black; font-size: 18px; text-align: center; } .calendar table { border-collapse: collapse; margin: 0 auto; width: 100%; height: 100%; table-layout: fixed; } .calendar th, .calendar td { border: 1px solid black; width: 14.28%; height: 14.28%; } .calendar .today { background-color: #ffcccc; } </style> </head> <body> <div class="calendar"> <h1>万年历</h1> <form> <label for="year">年份:</label> <input type="number" id="year" name="year" min="1900" max="2100" value="2021"> <label for="month">月份:</label> <select id="month" name="month"> <option value="1">1月</option> <option value="2">2月</option> <option value="3">3月</option> <option value="4">4月</option> <option value="5">5月</option> <option value="6">6月</option> <option value="7">7月</option> <option value="8">8月</option> <option value="9">9月</option> <option value="10">10月</option> <option value="11">11月</option> <option value="12">12月</option> </select> <input type="button" value="跳转" onclick="jump()"> </form> <table> <thead> <tr> <th>日</th> <th>一</th> <th>二</th> <th>三</th> <th>四</th> <th>五</th> <th>六</th> </tr> </thead> <tbody id="calendar-body"> </tbody> </table> </div> <script type="text/javascript"> function jump() { var year = document.getElementById("year").value; var month = document.getElementById("month").value; if (year && month) { var date = new Date(year, month - 1, 1); drawCalendar(date); } } function drawCalendar(date) { var year = date.getFullYear(); var month = date.getMonth() + 1; var firstDay = new Date(year, month - 1, 1); var lastDay = new Date(year, month, 0); var startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); var endDate = new Date(lastDay); endDate.setDate(endDate.getDate() + (6 - lastDay.getDay())); var calendarBody = document.getElementById("calendar-body"); calendarBody.innerHTML = ""; for (var d = startDate; d <= endDate; d.setDate(d.getDate() + 1)) { var tr = document.createElement("tr"); for (var i = 0; i < 7; i++) { var td = document.createElement("td"); if (d.getMonth() + 1 != month) { td.classList.add("other-month"); } if (d.getDate() == new Date().getDate() && d.getMonth() == new Date().getMonth() && d.getFullYear() == new Date().getFullYear()) { td.classList.add("today"); } td.innerText = d.getDate(); tr.appendChild(td); } calendarBody.appendChild(tr); } } window.onload = function() { var date = new Date(); drawCalendar(date); } </script> </body> </html> ``` 解释一下上面的代码: 1. 使用 HTML、CSS 和 JavaScript 创建一个名为 `calendar` 的 `div` 元素,用于显示万年历。 2. 在 `form` 中添加 `input` 元素和 `select` 元素,用于让用户选择年份和月份,并添加一个 `onclick` 事件,当用户点击“跳转”按钮时,执行 `jump()` 函数。 3. 在 `table` 中创建一个表格,包括表头和表格主体,用于显示每个月的日历。 4. 在 JavaScript 中,定义 `jump()` 函数用于跳转到用户选择的年份和月份,并调用 `drawCalendar()` 函数来绘制日历。 5. 在 `drawCalendar()` 函数中,根据指定的年份和月份计算出该月的第一天和最后一天,以及需要显示的日期范围。然后,通过循环在表格中生成每天的单元格,并对当前日期、非本月日期和节假日等进行不同的样式处理。 这样,一个简单的万年历面就完成了。用户可以在面中选择年份和月份,并跳转到指定的日历。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值