远程调用购物车服务来获取购物车中的所有购物项
遍历通过builderOrderItem方法构造订单项的数据(封装OrderItemEntity对象,指的单个订单项)
远程调用商品服务来获取spu的信息
设置商品的sku信息
使用StringUtils.collectionToDelimitedString将list集合转换为String
设置商品的积分信息
设置订单项的价格信息
iv、计算订单各种价格(总价,优惠价,积分价等等)
/** * 计算价格的方法 */ private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> orderItemEntities) { //总价 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 (OrderItemEntity orderItem : orderItemEntities) { //优惠券优惠总金额 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、订单价格相关的 orderEntity.setTotalAmount(total); //设置应付总额(总额+运费) orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount())); //设置优惠总价 orderEntity.setCouponAmount(coupon); //设置商品促销总价 orderEntity.setPromotionAmount(promotion); //设置积分优惠总价 orderEntity.setIntegrationAmount(intergration); //设置积分 orderEntity.setIntegration(integrationTotal); //设置成长值 orderEntity.setGrowth(growthTotal); //设置订单删除状态(0-未删除,1-已删除) orderEntity.setDeleteStatus(0); }
4、验证前端界面传来的价格和计算后的价格是否相同(验价,只要小于两个的差值0.01都可以接受)
5、保存订单信息
1>保存订单
2>保存订单项
/** * 保存订单所有数据 * @param orderCreateTo */ private void saveOrder(OrderCreateTo orderCreateTo) { //获取订单信息 OrderEntity order = orderCreateTo.getOrder(); order.setModifyTime(new Date()); order.setCreateTime(new Date()); //保存订单 this.baseMapper.insert(order); //获取订单项信息 List<OrderItemEntity> orderItems = orderCreateTo.getOrderItems(); //批量保存订单项数据(代码构造器自动生成的方法) orderItemService.saveBatch(orderItems); }
6、调用库存服务来进行锁库存(如果有异常要进行回滚,但是这里的回滚不是加个@Transactional可以实现的)
i、封装传给库存服务的WareSkuLockVo对象
package com.saodai.saodaimall.order.vo; import lombok.Data; import java.util.List; /** * 锁定库存的vo **/ @Data public class WareSkuLockVo { //订单号 private String orderSn; /** 需要锁住的所有库存信息 **/ private List<OrderItemVo> locks; }
package com.saodai.saodaimall.order.vo; import lombok.Data; import java.math.BigDecimal; import java.util.List; /** * 订单项类(其实就是购物项) **/ @Data public class OrderItemVo { private Long skuId; private Boolean check; private String title; private String image; /** * 商品套餐属性 */ private List<String> skuAttrValues; private BigDecimal price; private Integer count; private BigDecimal totalPrice; private Boolean hasStock; /** 商品重量 **/ private BigDecimal weight = new BigDecimal("0.085"); }
ii、远程调用功库存服务来锁库存
/** * 锁定库存 * @param vo * * 库存解锁的场景 * 1)、下订单成功,订单过期没有支付被系统自动取消或者被用户手动取消,都要解锁库存 * 2)、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁 * * * @return */ @PostMapping(value = "/lock/order") public R orderLockStock(@RequestBody WareSkuLockVo vo) { try { boolean lockStock = wareSkuService.orderLockStock(vo); return R.ok().setData(lockStock); } catch (NoStockException e) { // NO_STOCK_EXCEPTION(21000,"商品库存不足") return R.error(NO_STOCK_EXCEPTION.getCode(),NO_STOCK_EXCEPTION.getMessage()); } } /** * 为某个订单锁定库存 * @param vo * @return */ @Transactional(rollbackFor = Exception.class) @Override public boolean orderLockStock(WareSkuLockVo vo) { /** * * 用于解锁库存(回溯) */ //封装订单锁库存工作单实体类(表示我准备要给哪个订单锁库存了) WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity(); wareOrderTaskEntity.setOrderSn(vo.getOrderSn()); wareOrderTaskEntity.setCreateTime(new Date()); wareOrderTaskService.save(wareOrderTaskEntity); //1、按照下单的收货地址,找到一个就近仓库,锁定库存 //2、找到每个商品在哪个仓库都有库存 List<OrderItemVo> locks = vo.getLocks(); List<SkuWareHasStock> collect = locks.stream().map((item) -> { SkuWareHasStock stock = new SkuWareHasStock(); Long skuId = item.getSkuId(); stock.setSkuId(skuId); stock.setNum(item.getCount()); //查询这个商品在哪个仓库有库存 List<Long> wareIdList = wareSkuDao.listWareIdHasSkuStock(skuId); stock.setWareId(wareIdList); return stock; }).collect(Collectors.toList()); //2、锁定库存 for (SkuWareHasStock hasStock : collect) { //判断锁库存是否成功的标志位(只要有一个仓库锁定成功就可) boolean skuStocked = false; Long skuId = hasStock.getSkuId(); List<Long> wareIds = hasStock.getWareId(); if (StringUtils.isEmpty(wareIds)) { //没有任何仓库有这个商品的库存 throw new NoStockException(skuId); } //1、如果每一个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ //2、锁定失败。前面保存的工作单信息都回滚了。发送出去的消息,即使要解锁库存,由于在数据库查不到指定的id,所有就不用解锁 for (Long wareId : wareIds) { //进行锁库存操作(锁定成功就返回1,失败就返回0) Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum()); if (count == 1) { skuStocked = true; //封装库存工作单详情(具体给订单的哪个商品锁库存) WareOrderTaskDetailEntity taskDetailEntity = WareOrderTaskDetailEntity.builder() .skuId(skuId) .skuName("") .skuNum(hasStock.getNum()) .taskId(wareOrderTaskEntity.getId()) .wareId(wareId) .lockStatus(1) .build(); wareOrderTaskDetailService.save(taskDetailEntity); /**告诉RabbitMQ库存锁定成功**/ StockLockedTo lockedTo = new StockLockedTo(); //设置库存工作单的id(其实就是订单锁库存工作单的id号) lockedTo.setId(wareOrderTaskEntity.getId()); StockDetailTo detailTo = new StockDetailTo(); BeanUtils.copyProperties(taskDetailEntity,detailTo); lockedTo.setDetailTo(detailTo); /** * 把消息发给RabbitMQ队列的stock-event-exchange交换机 * 延时后的消息会被交换机stock-event-exchange根据路由键stock.release * (这个是MyRabbitMQConfig配置中的stockDelay方法设置的)发送到队列stock.release.stock.queue * 特别注意:只需要给交换机指定的路由键就可以路由到对应的队列,前提是要先在配置里设置好绑定关系 * */ rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo); break; } else { //当前仓库锁失败,重试下一个仓库 } } if (skuStocked == false) { //当前商品所有仓库都没有锁住 throw new NoStockException(skuId); } } //3、肯定全部都是锁定成功的 return true; }
- 封装WareOrderTaskEntity对象,用于后面异常时的数据回溯(表示我准备要给哪个订单锁库存了,对应数据库wms_ware_order_task表)
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 订单锁库存工作单(表示我准备要给哪个订单锁库存了) */ @Data @TableName("wms_ware_order_task") public class WareOrderTaskEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * order_id */ private Long orderId; /** * order_sn */ private String orderSn; /** * 收货人 */ private String consignee; /** * 收货人电话 */ private String consigneeTel; /** * 配送地址 */ private String deliveryAddress; /** * 订单备注 */ private String orderComment; /** * 付款方式【 1:在线付款 2:货到付款】 */ private Integer paymentWay; /** * 任务状态 */ private Integer taskStatus; /** * 订单描述 */ private String orderBody; /** * 物流单号 */ private String trackingNo; /** * create_time */ private Date createTime; /** * 仓库id */ private Long wareId; /** * 工作单备注 */ private String taskComment; }
- 找到每个商品在哪个仓库都有库存(封装 List<SkuWareHasStock>对象)
/**锁库存的内部类**/ @Data class SkuWareHasStock { //商品id private Long skuId; //商品数量 private Integer num; //商品所在仓库的id private List<Long> wareId; }
<!-- 查询这个商品在哪个仓库有库存--> <select id="listWareIdHasSkuStock" resultType="java.lang.Long"> SELECT ware_id FROM wms_ware_sku WHERE sku_id = #{skuId} AND stock - stock_locked > 0 </select>
锁定库存(修改需要锁库存的商品的锁库存数量和封装 WareOrderTaskDetailEntity对象)
遍历前面封装的 List<SkuWareHasStock>对象
设置判断锁库存是否成功的标志位(只要有一个仓库锁定成功就可)
如果 List<SkuWareHasStock>对象不为空就遍历每个库存仓库,调用lockSkuStock方法来锁库存
锁库存成功后把消息发给RabbitMQ队列
<!-- 锁定库存--> <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 > 0 </update>
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 库存工作单详情(具体给订单的哪个商品锁库存) */ @NoArgsConstructor @AllArgsConstructor @Builder @Data @TableName("wms_ware_order_task_detail") public class WareOrderTaskDetailEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * sku_id */ private Long skuId; /** * sku_name */ private String skuName; /** * 购买个数 */ private Integer skuNum; /** * 工作单id */ private Long taskId; /** * 仓库id */ private Long wareId; /** * 锁定状态 */ private Integer lockStatus; }
package com.saodai.common.to.mq; import lombok.Data; /** * 发送到mq消息队列的to **/ @Data public class StockLockedTo { /** 库存工作单的id **/ private Long id; /** 工作单详情的所有信息 StockDetailTo对象内容就是上面的WareOrderTaskDetailEntity **/ private StockDetailTo detailTo; }
7、解库存
锁库存成功后就会发消息给stock-event-exchange交换机,交换机根据路由键stock.locked把消息路由到stock.delay.queue延时队列(跟上面一样,这个延时队列的消息不会被消费掉),时间过期后就把消息根据路由键stock.release路由到stock.release.stock.queue队列,然后这个消息队列的消息是被一个专门解库存的监听器来监听(注意这里有两种解库存的监听方法,一个是自动解库存的监听,一个是订单服务的订单取消后立马解库存的监听)
rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
package com.saodai.saodaimall.ware.listener; import com.rabbitmq.client.Channel; import com.saodai.common.to.OrderTo; import com.saodai.common.to.mq.StockLockedTo; import com.saodai.saodaimall.ware.service.WareSkuService; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; /** * RabbitMQ的监听器 * 这里有两个监听方法,这两个监听识别的依据是看传入的是StockLockedTo还是OrderTo * 一个是监听的库存自动解锁 * 一个是监听订单取消后库存解锁 */ @Slf4j @RabbitListener(queues = "stock.release.stock.queue") @Service public class StockReleaseListener { @Autowired private WareSkuService wareSkuService; /** ** 监听库存自动解锁 */ @RabbitHandler public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException { log.info("******收到解锁库存的信息******"); try { System.out.println("******收到解锁库存的信息******"); //当前消息是否被第二次及以后(重新)派发过来了 // Boolean redelivered = message.getMessageProperties().getRedelivered(); //解锁库存 wareSkuService.unlockStock(to); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } /** * * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 *导致卡顿的订单,永远都不能解锁库存 * 订单释放直接和库存释放进行绑定 * @param orderTo * @param message * @param channel * @throws IOException */ @RabbitHandler public void handleOrderCloseRelease(OrderTo orderTo, Message message, Channel channel) throws IOException { log.info("******收到订单关闭,准备解锁库存的信息******"); try { wareSkuService.unlockStock(orderTo); // 手动删除消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (Exception e) { // 解锁失败 将消息重新放回队列,让别人消费 channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } }
package com.saodai.common.to.mq; import lombok.Data; /** * 发送到mq消息队列的to **/ @Data public class StockLockedTo { /** 库存工作单的id **/ private Long id; /** 工作单详情的所有信息 StockDetailTo对象内容就是上面的WareOrderTaskDetailEntity **/ private StockDetailTo detailTo; }
解锁库存的思路
首先查询数据库的库存详细工作单表看看有没有成功锁定库存(如果成功锁库存了会有对应的一条记录),如果没有那就说明库存没有锁成功,那自然就不需要解锁了
库存详细工作单表有这条记录那就证明库存锁定成功了
具体需不需要解库存还要先看订单状态
先查询有没有这个订单,没有这个订单必须解锁库存(可能出现因为有异常造成的数据回滚导致订单不存在的情况,但是库存锁成功了)
有这个订单,不一定解锁库存,要根据订单的状态来决定是否解库存
订单状态是已取消状态,说明是用户没有支付订单过期了,那就必须解锁库存
订单状态是已支付状态,说明是用户支付成功了,那就不能解锁库存
除了判断上面的情况,还有考虑当前库存详细工作单的状态,只有满足订单状态是已取消状态并且是已锁定的状态那才可以解库存
已锁定:解锁库存
已解锁 :不能再解锁
/** * (这个方法是由StockReleaseListener监听器调用的) * 锁库存失败后的自动解锁(也就是回溯) * @param to */ @Override public void unlockStock(StockLockedTo to) { //获取库存详细工作单类 StockDetailTo detail = to.getDetailTo(); //库存详细工作单的id Long detailId = detail.getId(); //WareOrderTaskDetailEntity是库存详细工作单类 WareOrderTaskDetailEntity taskDetailInfo = wareOrderTaskDetailService.getById(detailId); if (taskDetailInfo != null) { //查出wms_ware_order_task工作单的信息 Long id = to.getId(); //订单锁库存工作单(获取哪个订单要锁库存) WareOrderTaskEntity orderTaskInfo = wareOrderTaskService.getById(id); //获取订单号查询订单状态 String orderSn = orderTaskInfo.getOrderSn(); //远程查询订单信息 R orderData = orderFeignService.getOrderStatus(orderSn); if (orderData.getCode() == 0) { //订单数据返回成功 OrderVo orderInfo = orderData.getData("data", new TypeReference<OrderVo>() {}); /** * CREATE_NEW(0,"待付款"), * PAYED(1,"已付款"), * SENDED(2,"已发货"), * RECIEVED(3,"已完成"), * CANCLED(4,"已取消"), * SERVICING(5,"售后中"), * SERVICED(6,"售后完成"); */ //订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态(orderInfo.getStatus() == 4)才可解库存 if (orderInfo == null || orderInfo.getStatus() == 4) { //当前库存工作单详情状态1,已锁定,只有当前库存工作单详情状态未解锁才可以解锁 if (taskDetailInfo.getLockStatus() == 1) { //调用真正接库存的方法unLockStock unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId); } } } else { //消息拒绝以后重新放在队列里面,让别人继续消费解锁 //远程调用服务失败 throw new RuntimeException("远程调用服务失败"); } } else { //无需解锁 } } /** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); } /** * 订单取消了就立马解库存 * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 * 导致卡顿的订单,永远都不能解锁库存 * @param orderTo */ @Transactional(rollbackFor = Exception.class) @Override public void unlockStock(OrderTo orderTo) { String orderSn = orderTo.getOrderSn(); //查一下最新的库存解锁状态,防止重复解锁库存 WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn); //按照工作单的id找到所有 没有解锁的库存,进行解锁(lock_status=1表示已锁定库存) Long id = orderTaskEntity.getId(); List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>() .eq("task_id", id).eq("lock_status", 1)); for (WareOrderTaskDetailEntity taskDetailEntity : list) { //解锁库存 unLockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum(), taskDetailEntity.getId()); } }
自动解库存
/** * (这个方法是由StockReleaseListener监听器调用的) * 锁库存失败后的自动解锁(也就是回溯) * @param to */ @Override public void unlockStock(StockLockedTo to) { //获取库存详细工作单类 StockDetailTo detail = to.getDetailTo(); //库存详细工作单的id Long detailId = detail.getId(); //WareOrderTaskDetailEntity是库存详细工作单类 WareOrderTaskDetailEntity taskDetailInfo = wareOrderTaskDetailService.getById(detailId); if (taskDetailInfo != null) { //查出wms_ware_order_task工作单的信息 Long id = to.getId(); //订单锁库存工作单(获取哪个订单要锁库存) WareOrderTaskEntity orderTaskInfo = wareOrderTaskService.getById(id); //获取订单号查询订单状态 String orderSn = orderTaskInfo.getOrderSn(); //远程查询订单信息 R orderData = orderFeignService.getOrderStatus(orderSn); if (orderData.getCode() == 0) { //订单数据返回成功 OrderVo orderInfo = orderData.getData("data", new TypeReference<OrderVo>() {}); /** * CREATE_NEW(0,"待付款"), * PAYED(1,"已付款"), * SENDED(2,"已发货"), * RECIEVED(3,"已完成"), * CANCLED(4,"已取消"), * SERVICING(5,"售后中"), * SERVICED(6,"售后完成"); */ //订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态(orderInfo.getStatus() == 4)才可解库存 if (orderInfo == null || orderInfo.getStatus() == 4) { //当前库存工作单详情状态1,已锁定,只有当前库存工作单详情状态未解锁才可以解锁 if (taskDetailInfo.getLockStatus() == 1) { //调用真正接库存的方法unLockStock unLockStock(detail.getSkuId(),detail.getWareId(),detail.getSkuNum(),detailId); } } } else { //消息拒绝以后重新放在队列里面,让别人继续消费解锁 //远程调用服务失败 throw new RuntimeException("远程调用服务失败"); } } else { //无需解锁 } } /** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); }
- 自动解库存的具体实现流程
- 获取库存详细工作单的id
package com.saodai.common.to.mq; import lombok.Data; /** * 发送到mq消息队列的to **/ @Data public class StockLockedTo { /** 库存工作单的id **/ private Long id; /** 工作单详情的所有信息 **/ private StockDetailTo detailTo; }
package com.saodai.common.to.mq; import lombok.Data; /** * 其实就是库存工作单详情实体类(具体给订单的哪个商品锁库存) **/ @Data public class StockDetailTo { private Long id; /** * sku_id */ private Long skuId; /** * sku_name */ private String skuName; /** * 购买个数 */ private Integer skuNum; /** * 工作单id */ private Long taskId; /** * 仓库id */ private Long wareId; /** * 锁定状态 */ private Integer lockStatus; }
- 查询数据库有没有这个库存详细工作单类
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 库存工作单详情(具体给订单的哪个商品锁库存) */ @NoArgsConstructor @AllArgsConstructor @Builder @Data @TableName("wms_ware_order_task_detail") public class WareOrderTaskDetailEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * sku_id */ private Long skuId; /** * sku_name */ private String skuName; /** * 购买个数 */ private Integer skuNum; /** * 工作单id */ private Long taskId; /** * 仓库id */ private Long wareId; /** * 锁定状态 */ private Integer lockStatus; }
- 查询订单锁库存工作单(获取哪个订单要锁库存)
package com.saodai.saodaimall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 订单锁库存工作单(表示我准备要给哪个订单锁库存了) */ @Data @TableName("wms_ware_order_task") public class WareOrderTaskEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * order_id */ private Long orderId; /** * order_sn */ private String orderSn; /** * 收货人 */ private String consignee; /** * 收货人电话 */ private String consigneeTel; /** * 配送地址 */ private String deliveryAddress; /** * 订单备注 */ private String orderComment; /** * 付款方式【 1:在线付款 2:货到付款】 */ private Integer paymentWay; /** * 任务状态 */ private Integer taskStatus; /** * 订单描述 */ private String orderBody; /** * 物流单号 */ private String trackingNo; /** * create_time */ private Date createTime; /** * 仓库id */ private Long wareId; /** * 工作单备注 */ private String taskComment; }
- 根据订单号远程查询订单
package com.saodai.saodaimall.ware.vo; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class OrderVo { private Long id; /** * member_id */ private Long memberId; /** * 订单号 */ private String orderSn; /** * 使用的优惠券 */ private Long couponId; /** * create_time */ private Date createTime; /** * 用户名 */ private String memberUsername; /** * 订单总额 */ private BigDecimal totalAmount; /** * 应付总额 */ private BigDecimal payAmount; /** * 运费金额 */ private BigDecimal freightAmount; /** * 促销优化金额(促销价、满减、阶梯价) */ private BigDecimal promotionAmount; /** * 积分抵扣金额 */ private BigDecimal integrationAmount; /** * 优惠券抵扣金额 */ private BigDecimal couponAmount; /** * 后台调整订单使用的折扣金额 */ private BigDecimal discountAmount; /** * 支付方式【1->支付宝;2->微信;3->银联; 4->货到付款;】 */ private Integer payType; /** * 订单来源[0->PC订单;1->app订单] */ private Integer sourceType; /** * 订单状态【0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单】 */ private Integer status; /** * 物流公司(配送方式) */ private String deliveryCompany; /** * 物流单号 */ private String deliverySn; /** * 自动确认时间(天) */ private Integer autoConfirmDay; /** * 可以获得的积分 */ private Integer integration; /** * 可以获得的成长值 */ private Integer growth; /** * 发票类型[0->不开发票;1->电子发票;2->纸质发票] */ private Integer billType; /** * 发票抬头 */ private String billHeader; /** * 发票内容 */ private String billContent; /** * 收票人电话 */ private String billReceiverPhone; /** * 收票人邮箱 */ private String billReceiverEmail; /** * 收货人姓名 */ private String receiverName; /** * 收货人电话 */ private String receiverPhone; /** * 收货人邮编 */ private String receiverPostCode; /** * 省份/直辖市 */ private String receiverProvince; /** * 城市 */ private String receiverCity; /** * 区 */ private String receiverRegion; /** * 详细地址 */ private String receiverDetailAddress; /** * 订单备注 */ private String note; /** * 确认收货状态[0->未确认;1->已确认] */ private Integer confirmStatus; /** * 删除状态【0->未删除;1->已删除】 */ private Integer deleteStatus; /** * 下单时使用的积分 */ private Integer useIntegration; /** * 支付时间 */ private Date paymentTime; /** * 发货时间 */ private Date deliveryTime; /** * 确认收货时间 */ private Date receiveTime; /** * 评价时间 */ private Date commentTime; /** * 修改时间 */ private Date modifyTime; }
进行双重判断
先判断订单不存在(因为有异常造成的数据回滚导致订单不存在)或者订单状态是取消状态
在判断当前库存工作单详情状态是不是1,1表示已锁定,只有当前库存工作单详情状态未解锁才可以解锁
调用unLockStock方法实现真正的解库存(自动解库存)
更新库存的数量(还原)
更新工作单的状态为已解锁
/** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); }
<!-- 解锁库存--> <update id="unLockStock"> UPDATE wms_ware_sku SET stock_locked = stock_locked - #{num} WHERE sku_id = ${skuId} AND ware_id = #{wareId} </update>
手动解库存
- 订单服务的订单取消后立马解库存的具体逻辑
- 通过订单锁库存工作单的id去库存详细工作单去找对应的锁库存的记录,看有没有记录并且锁库存的状态是已锁定的状态,防止多次重复解库存(其中库存详细工作单中的工作id的值就是订单锁库存工作单的id的值)
- 最后调用真正的解库存方法来解库存
/** * 真正解锁库存的方法(自动解库存) * @param skuId 需要解锁库存的商品id * @param wareId 需要解锁库存的库存仓库id * @param num 需要解锁库存的商品数量 * @param taskDetailId 库存工作单详情id */ public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) { //库存解锁(其实就是修改wms_ware_sku表中的stock_locked的值,之前锁库存锁了多少个就减去多少个) wareSkuDao.unLockStock(skuId,wareId,num); //更新工作单的状态 WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(); taskDetailEntity.setId(taskDetailId); //setLockStatus(2)表示变为已解锁(1表示已锁定,2表示已解锁,3表示减扣) taskDetailEntity.setLockStatus(2); wareOrderTaskDetailService.updateById(taskDetailEntity); } /** * 订单取消了就立马解库存 * 防止订单服务卡顿,导致订单状态消息一直改不了,库存优先到期,查订单状态新建,什么都不处理 * 导致卡顿的订单,永远都不能解锁库存 * @param orderTo */ @Transactional(rollbackFor = Exception.class) @Override public void unlockStock(OrderTo orderTo) { String orderSn = orderTo.getOrderSn(); //查一下最新的库存解锁状态,防止重复解锁库存 WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getOrderTaskByOrderSn(orderSn); //按照工作单的id找到所有 没有解锁的库存,进行解锁(lock_status=1表示已锁定库存) Long id = orderTaskEntity.getId(); List<WareOrderTaskDetailEntity> list = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>() .eq("task_id", id).eq("lock_status", 1)); for (WareOrderTaskDetailEntity taskDetailEntity : list) { //解锁库存 unLockStock(taskDetailEntity.getSkuId(), taskDetailEntity.getWareId(), taskDetailEntity.getSkuNum(), taskDetailEntity.getId()); } }