提交订单到支付页功能实现
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循环中太多远程调用,吞吐量太低