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