From 04d98a1cb070d61a3f6f5f432a2a32c1004b0bd0 Mon Sep 17 00:00:00 2001 From: Kirk Lin Date: Tue, 13 Jul 2021 17:10:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=95=86=E5=9F=8E=E4=B8=9A=E5=8A=A1=20-->=20?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E6=8F=90=E4=BA=A4=E5=9F=BA=E6=9C=AC=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kkmall/order/feign/WmsFeignService.java | 14 +- .../kkmall/order/service/OrderService.java | 6 + .../order/service/impl/OrderServiceImpl.java | 268 +++++++++++++++++- .../lkk/kkmall/order/to/OrderCreateTo.java | 28 ++ .../name/lkk/kkmall/order/vo/PayAsyncVo.java | 85 ++++++ .../java/name/lkk/kkmall/order/vo/PayVo.java | 26 ++ .../lkk/kkmall/order/vo/WareSkuLockVo.java | 21 ++ .../kkmall/order/web/OrderWebController.java | 46 +++ .../ware/controller/WareSkuController.java | 20 +- .../name/lkk/kkmall/ware/dao/WareSkuDao.java | 18 +- .../entity/WareOrderTaskDetailEntity.java | 7 +- .../kkmall/ware/service/WareSkuService.java | 6 + .../ware/service/impl/WareSkuServiceImpl.java | 77 ++++- .../name/lkk/kkmall/ware/vo/OrderItemVo.java | 46 +++ .../java/name/lkk/kkmall/ware/vo/OrderVo.java | 177 ++++++++++++ .../lkk/kkmall/ware/vo/SkuWareHasStock.java | 19 ++ .../lkk/kkmall/ware/vo/WareSkuLockVo.java | 21 ++ .../main/resources/mapper/ware/WareSkuDao.xml | 29 +- 18 files changed, 895 insertions(+), 19 deletions(-) create mode 100644 kkmall-order/src/main/java/name/lkk/kkmall/order/to/OrderCreateTo.java create mode 100644 kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayAsyncVo.java create mode 100644 kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayVo.java create mode 100644 kkmall-order/src/main/java/name/lkk/kkmall/order/vo/WareSkuLockVo.java create mode 100644 kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderItemVo.java create mode 100644 kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderVo.java create mode 100644 kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/SkuWareHasStock.java create mode 100644 kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/WareSkuLockVo.java diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/feign/WmsFeignService.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/feign/WmsFeignService.java index e4cff70..62a0ff2 100644 --- a/kkmall-order/src/main/java/name/lkk/kkmall/order/feign/WmsFeignService.java +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/feign/WmsFeignService.java @@ -2,9 +2,12 @@ package name.lkk.kkmall.order.feign; import name.lkk.common.utils.R; +import name.lkk.kkmall.order.vo.WareSkuLockVo; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @@ -14,4 +17,13 @@ public interface WmsFeignService { @PostMapping("/ware/waresku/hasStock") R getSkuHasStock(@RequestBody List SkuIds); -} + @GetMapping("/ware/wareinfo/fare") + R getFare(@RequestParam("addrId") Long addrId); + + /** + * 锁定库存 + */ + @PostMapping("/ware/waresku/lock/order") + R orderLockStock(@RequestBody WareSkuLockVo vo); + +} \ No newline at end of file diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/service/OrderService.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/service/OrderService.java index 5bcd52d..9ba266d 100644 --- a/kkmall-order/src/main/java/name/lkk/kkmall/order/service/OrderService.java +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/service/OrderService.java @@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService; import name.lkk.common.utils.PageUtils; import name.lkk.kkmall.order.entity.OrderEntity; import name.lkk.kkmall.order.vo.OrderConfirmVo; +import name.lkk.kkmall.order.vo.OrderSubmitVo; +import name.lkk.kkmall.order.vo.SubmitOrderResponseVo; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -20,5 +22,9 @@ public interface OrderService extends IService { PageUtils queryPage(Map params); OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException; + + SubmitOrderResponseVo submitOrder(OrderSubmitVo submitVo); + + } diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/service/impl/OrderServiceImpl.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/service/impl/OrderServiceImpl.java index 0626a8b..588c664 100644 --- a/kkmall-order/src/main/java/name/lkk/kkmall/order/service/impl/OrderServiceImpl.java +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/service/impl/OrderServiceImpl.java @@ -3,8 +3,11 @@ package name.lkk.kkmall.order.service.impl; import com.alibaba.fastjson.TypeReference; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; +import name.lkk.common.enume.OrderStatusEnum; +import name.lkk.common.exception.NotStockException; import name.lkk.common.utils.PageUtils; import name.lkk.common.utils.Query; import name.lkk.common.utils.R; @@ -12,24 +15,27 @@ import name.lkk.common.vo.MemberRsepVo; import name.lkk.kkmall.order.constant.OrderConstant; import name.lkk.kkmall.order.dao.OrderDao; import name.lkk.kkmall.order.entity.OrderEntity; +import name.lkk.kkmall.order.entity.OrderItemEntity; import name.lkk.kkmall.order.feign.CartFeignService; import name.lkk.kkmall.order.feign.MemberFeignService; +import name.lkk.kkmall.order.feign.ProductFeignService; import name.lkk.kkmall.order.feign.WmsFeignService; import name.lkk.kkmall.order.interceptor.LoginUserInterceptor; +import name.lkk.kkmall.order.service.OrderItemService; import name.lkk.kkmall.order.service.OrderService; -import name.lkk.kkmall.order.vo.MemberAddressVo; -import name.lkk.kkmall.order.vo.OrderConfirmVo; -import name.lkk.kkmall.order.vo.OrderItemVo; -import name.lkk.kkmall.order.vo.SkuStockVo; +import name.lkk.kkmall.order.to.OrderCreateTo; +import name.lkk.kkmall.order.vo.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.math.BigDecimal; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadPoolExecutor; @@ -40,21 +46,30 @@ import java.util.stream.Collectors; @Service("orderService") public class OrderServiceImpl extends ServiceImpl implements OrderService { + @Autowired + private ThreadPoolExecutor executor; + + @Autowired + private OrderItemService orderItemService; + @Autowired private MemberFeignService memberFeignService; @Autowired private CartFeignService cartFeignService; - @Autowired - private ThreadPoolExecutor executor; - @Autowired private WmsFeignService wmsFeignService; + @Autowired + private ProductFeignService productFeignService; + @Autowired private StringRedisTemplate stringRedisTemplate; + private ThreadLocal confirmVoThreadLocal = new ThreadLocal<>(); + + @Override public PageUtils queryPage(Map params) { IPage page = this.page( @@ -124,4 +139,237 @@ public class OrderServiceImpl extends ServiceImpl impleme return confirmVo; } + // @GlobalTransactional + @Transactional + @Override + public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) { + // 当条线程共享这个对象 + confirmVoThreadLocal.set(vo); + SubmitOrderResponseVo submitVo = new SubmitOrderResponseVo(); + // 0:正常 + submitVo.setCode(0); + // 去服务器创建订单,验令牌,验价格,所库存 + MemberRsepVo memberRsepVo = LoginUserInterceptor.threadLocal.get(); + // 1. 验证令牌 [必须保证原子性] 返回 0 or 1 + // 0 令牌删除失败 1删除成功 + String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; + String orderToken = vo.getOrderToken(); + + // 原子验证令牌 删除令牌 + Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRsepVo.getId()), orderToken); + if (result == 0L) { + // 令牌验证失败 + submitVo.setCode(1); + } else { + // 令牌验证成功 + // 1 .创建订单等信息 + OrderCreateTo order = createOrder(); + // 2. 验价 + BigDecimal payAmount = order.getOrder().getPayAmount(); + BigDecimal voPayPrice = vo.getPayPrice(); + if (Math.abs(payAmount.subtract(voPayPrice).doubleValue()) < 0.01) { + // 金额对比成功 + // 3.保存订单 + saveOrder(order); + // 4.库存锁定 + WareSkuLockVo lockVo = new WareSkuLockVo(); + lockVo.setOrderSn(order.getOrder().getOrderSn()); + List locks = order.getOrderItems().stream().map(item -> { + OrderItemVo itemVo = new OrderItemVo(); + // 锁定的skuId 这个skuId要锁定的数量 + itemVo.setSkuId(item.getSkuId()); + itemVo.setCount(item.getSkuQuantity()); + itemVo.setTitle(item.getSkuName()); + return itemVo; + }).collect(Collectors.toList()); + + lockVo.setLocks(locks); + // 远程锁库存 + R r = wmsFeignService.orderLockStock(lockVo); + if (r.getCode() == 0) { + // 库存足够 锁定成功 给MQ发送消息 + submitVo.setOrderEntity(order.getOrder()); +// rabbitTemplate.convertAndSend(this.eventExchange, this.createOrder, order.getOrder()); +// int i = 10/0; + } else { + // 锁定失败 + String msg = (String) r.get("msg"); + throw new NotStockException(msg); + } + } else { + // 价格验证失败 + submitVo.setCode(2); + } + } + return submitVo; + } + + + /** + * 保存订单所有数据 + */ + private void saveOrder(OrderCreateTo order) { + OrderEntity orderEntity = order.getOrder(); + orderEntity.setModifyTime(new Date()); + this.save(orderEntity); + + List orderItems = order.getOrderItems(); + orderItems = orderItems.stream().map(item -> { + item.setOrderId(orderEntity.getId()); + item.setSpuName(item.getSpuName()); + item.setOrderSn(order.getOrder().getOrderSn()); + return item; + }).collect(Collectors.toList()); + orderItemService.saveBatch(orderItems); + } + + /** + * 创建订单 + */ + private OrderCreateTo createOrder() { + + OrderCreateTo orderCreateTo = new OrderCreateTo(); + // 1. 生成一个订单号 + String orderSn = IdWorker.getTimeId(); + OrderEntity orderEntity = buildOrderSn(orderSn); + + // 2. 获取所有订单项 + List items = buildOrderItems(orderSn); + + // 3.验价 传入订单 、订单项 计算价格、积分、成长值等相关信息 + computerPrice(orderEntity, items); + orderCreateTo.setOrder(orderEntity); + orderCreateTo.setOrderItems(items); + return orderCreateTo; + } + + private void computerPrice(OrderEntity orderEntity, List items) { + BigDecimal totalPrice = new BigDecimal("0.0"); + // 叠加每一个订单项的金额 + BigDecimal coupon = new BigDecimal("0.0"); + BigDecimal integration = new BigDecimal("0.0"); + BigDecimal promotion = new BigDecimal("0.0"); + BigDecimal gift = new BigDecimal("0.0"); + BigDecimal growth = new BigDecimal("0.0"); + for (OrderItemEntity item : items) { + // 优惠券的金额 + coupon = coupon.add(item.getCouponAmount()); + // 积分优惠的金额 + integration = integration.add(item.getIntegrationAmount()); + // 打折的金额 + promotion = promotion.add(item.getPromotionAmount()); + BigDecimal realAmount = item.getRealAmount(); + totalPrice = totalPrice.add(realAmount); + + // 购物获取的积分、成长值 + gift.add(new BigDecimal(item.getGiftIntegration().toString())); + growth.add(new BigDecimal(item.getGiftGrowth().toString())); + } + // 1.订单价格相关 总额、应付总额 + orderEntity.setTotalAmount(totalPrice); + orderEntity.setPayAmount(totalPrice.add(orderEntity.getFreightAmount())); + + orderEntity.setPromotionAmount(promotion); + orderEntity.setIntegrationAmount(integration); + orderEntity.setCouponAmount(coupon); + + // 设置积分、成长值 + orderEntity.setIntegration(gift.intValue()); + orderEntity.setGrowth(growth.intValue()); + + // 设置订单的删除状态 + orderEntity.setDeleteStatus(OrderStatusEnum.CREATE_NEW.getCode()); + } + + /** + * 为 orderSn 订单构建订单数据 + */ + private List buildOrderItems(String orderSn) { + // 这里是最后一次来确认购物项的价格 这个远程方法还会查询一次数据库 + List cartItems = cartFeignService.getCurrentUserCartItems(); + List itemEntities = null; + if (cartItems != null && cartItems.size() > 0) { + itemEntities = cartItems.stream().map(cartItem -> { + OrderItemEntity itemEntity = buildOrderItem(cartItem); + itemEntity.setOrderSn(orderSn); + return itemEntity; + }).collect(Collectors.toList()); + } + return itemEntities; + } + + /** + * 构建某一个订单项 + */ + private OrderItemEntity buildOrderItem(OrderItemVo cartItem) { + OrderItemEntity itemEntity = new OrderItemEntity(); + // 1.订单信息: 订单号 + + // 2.商品spu信息 + Long skuId = cartItem.getSkuId(); + R r = productFeignService.getSkuInfoBySkuId(skuId); + SpuInfoVo spuInfo = r.getData(new TypeReference() { + }); + itemEntity.setSpuId(spuInfo.getId()); + itemEntity.setSpuBrand(spuInfo.getBrandId().toString()); + itemEntity.setSpuName(spuInfo.getSpuName()); + itemEntity.setCategoryId(spuInfo.getCatalogId()); + // 3.商品的sku信息 + itemEntity.setSkuId(cartItem.getSkuId()); + itemEntity.setSkuName(cartItem.getTitle()); + itemEntity.setSkuPic(cartItem.getImage()); + itemEntity.setSkuPrice(cartItem.getPrice()); + // 把一个集合按照指定的字符串进行分割得到一个字符串 + String skuAttr = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";"); + itemEntity.setSkuAttrsVals(skuAttr); + itemEntity.setSkuQuantity(cartItem.getCount()); + // 4.积分信息 买的数量越多积分越多 成长值越多 + itemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())).intValue()); + itemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())).intValue()); + // 5.订单项的价格信息 优惠金额 + itemEntity.setPromotionAmount(new BigDecimal("0.0")); + itemEntity.setCouponAmount(new BigDecimal("0.0")); + itemEntity.setIntegrationAmount(new BigDecimal("0.0")); + // 当前订单项的实际金额 + BigDecimal orign = itemEntity.getSkuPrice().multiply(new BigDecimal(itemEntity.getSkuQuantity().toString())); + // 减去各种优惠的价格 + BigDecimal subtract = orign.subtract(itemEntity.getCouponAmount()).subtract(itemEntity.getPromotionAmount()).subtract(itemEntity.getIntegrationAmount()); + itemEntity.setRealAmount(subtract); + return itemEntity; + } + + /** + * 构建一个订单 + */ + private OrderEntity buildOrderSn(String orderSn) { + OrderEntity entity = new OrderEntity(); + entity.setOrderSn(orderSn); + entity.setCreateTime(new Date()); + entity.setCommentTime(new Date()); + entity.setReceiveTime(new Date()); + entity.setDeliveryTime(new Date()); + MemberRsepVo rsepVo = LoginUserInterceptor.threadLocal.get(); + entity.setMemberId(rsepVo.getId()); + entity.setMemberUsername(rsepVo.getUsername()); + entity.setBillReceiverEmail(rsepVo.getEmail()); + // 2. 获取收获地址信息 + OrderSubmitVo submitVo = confirmVoThreadLocal.get(); + R fare = wmsFeignService.getFare(submitVo.getAddrId()); + FareVo resp = fare.getData(new TypeReference() { + }); + entity.setFreightAmount(resp.getFare()); + entity.setReceiverCity(resp.getMemberAddressVo().getCity()); + entity.setReceiverDetailAddress(resp.getMemberAddressVo().getDetailAddress()); + entity.setDeleteStatus(OrderStatusEnum.CREATE_NEW.getCode()); + entity.setReceiverPhone(resp.getMemberAddressVo().getPhone()); + entity.setReceiverName(resp.getMemberAddressVo().getName()); + entity.setReceiverPostCode(resp.getMemberAddressVo().getPostCode()); + entity.setReceiverProvince(resp.getMemberAddressVo().getProvince()); + entity.setReceiverRegion(resp.getMemberAddressVo().getRegion()); + // 设置订单状态 + entity.setStatus(OrderStatusEnum.CREATE_NEW.getCode()); + entity.setAutoConfirmDay(7); + return entity; + } + } \ No newline at end of file diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/to/OrderCreateTo.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/to/OrderCreateTo.java new file mode 100644 index 0000000..01cea0a --- /dev/null +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/to/OrderCreateTo.java @@ -0,0 +1,28 @@ +package name.lkk.kkmall.order.to; + + +import lombok.Data; +import name.lkk.kkmall.order.entity.OrderEntity; +import name.lkk.kkmall.order.entity.OrderItemEntity; + +import java.math.BigDecimal; +import java.util.List; + + +@Data +public class OrderCreateTo { + + private OrderEntity order; + + private List orderItems; + + /** + * 订单计算的应付价格 + */ + private BigDecimal payPrice; + + /** + * 运费 + */ + private BigDecimal fare; +} diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayAsyncVo.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayAsyncVo.java new file mode 100644 index 0000000..8227ba9 --- /dev/null +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayAsyncVo.java @@ -0,0 +1,85 @@ +package name.lkk.kkmall.order.vo; + +import lombok.Data; + +import java.util.Date; + +@Data +public class PayAsyncVo { + + private Date gmt_create; + + private String charset; + + private String gmt_payment; + + private Date notify_time; + + private String subject; + + private String sign; + + private String buyer_id;//支付者的id + + private String body;//订单的信息 + + private String invoice_amount;//支付金额 + + private String version; + + private String notify_id;//通知id + + private String fund_bill_list; + + private String notify_type;//通知类型; trade_status_sync + + private String out_trade_no;//订单号 + + private String total_amount;//支付的总额 + + private String trade_status;//交易状态 TRADE_SUCCESS + + private String trade_no;//流水号 + + private String auth_app_id; + + private String receipt_amount;//商家收到的款 + + private String point_amount;// + + private String app_id;//应用id + + private String buyer_pay_amount;//最终支付的金额 + + private String sign_type;//签名类型 + + private String seller_id;//商家的id + + @Override + public String toString() { + return "gmt_create --> '" + gmt_create + '\'' + + "\ncharset --> '" + charset + '\'' + + "\ngmt_payment --> '" + gmt_payment + '\'' + + "\nnotify_time --> '" + notify_time + '\'' + + "\nsubject --> '" + subject + '\'' + + "\nsign --> '" + sign + '\'' + + "\nbuyer_id --> '" + buyer_id + '\'' + + "\nbody --> '" + body + '\'' + + "\ninvoice_amount --> '" + invoice_amount + '\'' + + "\nversion --> '" + version + '\'' + + "\nnotify_id --> '" + notify_id + '\'' + + "\nfund_bill_list --> '" + fund_bill_list + '\'' + + "\nnotify_type --> '" + notify_type + '\'' + + "\nout_trade_no --> '" + out_trade_no + '\'' + + "\ntotal_amount --> '" + total_amount + '\'' + + "\ntrade_status --> '" + trade_status + '\'' + + "\ntrade_no --> '" + trade_no + '\'' + + "\nauth_app_id --> '" + auth_app_id + '\'' + + "\nreceipt_amount --> '" + receipt_amount + '\'' + + "\npoint_amount --> '" + point_amount + '\'' + + "\napp_id --> '" + app_id + '\'' + + "\nbuyer_pay_amount --> '" + buyer_pay_amount + '\'' + + "\nsign_type --> '" + sign_type + '\'' + + "\nseller_id --> '" + seller_id + '\''; + } +} diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayVo.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayVo.java new file mode 100644 index 0000000..f494035 --- /dev/null +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/PayVo.java @@ -0,0 +1,26 @@ +package name.lkk.kkmall.order.vo; + +import lombok.Data; + +@Data +public class PayVo { + /** + * 商户订单号 必填 + */ + private String out_trade_no; + + /** + * 订单名称 必填 + */ + private String subject; + + /** + * 付款金额 必填 + */ + private String total_amount; + + /** + * 商品描述 可空 + */ + private String body; +} diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/WareSkuLockVo.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/WareSkuLockVo.java new file mode 100644 index 0000000..e956929 --- /dev/null +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/vo/WareSkuLockVo.java @@ -0,0 +1,21 @@ +package name.lkk.kkmall.order.vo; + +import lombok.Data; + +import java.util.List; + + +@Data +public class WareSkuLockVo { + + /** + * 订单号 + */ + private String orderSn; + + /** + * 要锁住的所有库存信息 + */ + private List locks; + +} diff --git a/kkmall-order/src/main/java/name/lkk/kkmall/order/web/OrderWebController.java b/kkmall-order/src/main/java/name/lkk/kkmall/order/web/OrderWebController.java index 5f7d6a8..8b005db 100644 --- a/kkmall-order/src/main/java/name/lkk/kkmall/order/web/OrderWebController.java +++ b/kkmall-order/src/main/java/name/lkk/kkmall/order/web/OrderWebController.java @@ -1,12 +1,17 @@ package name.lkk.kkmall.order.web; import lombok.extern.slf4j.Slf4j; +import name.lkk.common.exception.NotStockException; import name.lkk.kkmall.order.service.OrderService; import name.lkk.kkmall.order.vo.OrderConfirmVo; +import name.lkk.kkmall.order.vo.OrderSubmitVo; +import name.lkk.kkmall.order.vo.SubmitOrderResponseVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.util.concurrent.ExecutionException; @@ -31,4 +36,45 @@ public class OrderWebController { return "confirm"; } + /** + * 下单功能 + */ + @PostMapping("/submitOrder") + public String submitOrder(OrderSubmitVo submitVo, Model model, RedirectAttributes redirectAttributes) { + + try { + SubmitOrderResponseVo responseVo = orderService.submitOrder(submitVo); + // 下单失败回到订单重新确认订单信息 + 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; + default: + msg += "未知错误"; + } + + redirectAttributes.addFlashAttribute("msg", msg); + return "redirect:http://order.kkmall.com/toTrade"; + } + } catch (Exception e) { + if (e instanceof NotStockException) { + String message = e.getMessage(); + redirectAttributes.addFlashAttribute("msg", message); + } + return "redirect:http://order.kkmall.com/toTrade"; + } + } } diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/controller/WareSkuController.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/controller/WareSkuController.java index ae9e929..28d333c 100644 --- a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/controller/WareSkuController.java +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/controller/WareSkuController.java @@ -1,10 +1,14 @@ package name.lkk.kkmall.ware.controller; +import lombok.extern.slf4j.Slf4j; +import name.lkk.common.exception.BizCodeEnum; +import name.lkk.common.exception.NotStockException; import name.lkk.common.to.es.SkuHasStockVo; import name.lkk.common.utils.PageUtils; import name.lkk.common.utils.R; import name.lkk.kkmall.ware.entity.WareSkuEntity; import name.lkk.kkmall.ware.service.WareSkuService; +import name.lkk.kkmall.ware.vo.WareSkuLockVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -21,17 +25,31 @@ import java.util.Map; * @email linkirk@163.com * @date 2021-06-07 16:44:32 */ +@Slf4j @RestController @RequestMapping("ware/waresku") public class WareSkuController { @Autowired private WareSkuService wareSkuService; + + @PostMapping("/lock/order") + public R orderLockStock(@RequestBody WareSkuLockVo vo) { + try { + wareSkuService.orderLockStock(vo); + return R.ok(); + } catch (NotStockException e) { + log.warn("\n" + e.getMessage()); + } + return R.error(BizCodeEnum.NOT_STOCK_EXCEPTION.getCode(), BizCodeEnum.NOT_STOCK_EXCEPTION.getMsg()); + } + + /** * 查询sku是否有库存 * 返回当前id stock量 */ @PostMapping("/hasStock") - public R getSkuHasStock(@RequestBody List skuIds){ + public R getSkuHasStock(@RequestBody List skuIds) { List vos = wareSkuService.getSkuHasStock(skuIds); return R.ok().setData(vos); } diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/dao/WareSkuDao.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/dao/WareSkuDao.java index 4185666..2f65839 100644 --- a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/dao/WareSkuDao.java +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/dao/WareSkuDao.java @@ -5,9 +5,11 @@ import name.lkk.kkmall.ware.entity.WareSkuEntity; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * 商品库存 - * + * * @author KirkLin * @email linkirk@163.com * @date 2021-06-07 16:44:32 @@ -15,12 +17,26 @@ import org.apache.ibatis.annotations.Param; @Mapper public interface WareSkuDao extends BaseMapper { + /** + * 更新库存 + * + * @param skuId + * @param wareId + * @param skuNum + */ void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum); /** * 查询是否有库存 + * * @param id * @return */ Long getSkuStock(Long id); + + List listWareIdHasSkuStock(@Param("skuId") Long skuId); + + Long lockSkuStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("num") Integer num); + + void unlockStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("num") Integer num); } diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/entity/WareOrderTaskDetailEntity.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/entity/WareOrderTaskDetailEntity.java index 7e63e88..02b33f3 100644 --- a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/entity/WareOrderTaskDetailEntity.java +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/entity/WareOrderTaskDetailEntity.java @@ -2,10 +2,11 @@ package name.lkk.kkmall.ware.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; -import java.util.Date; -import lombok.Data; /** * 库存工作单 @@ -15,6 +16,8 @@ import lombok.Data; * @date 2021-06-07 16:44:32 */ @Data +@AllArgsConstructor +@NoArgsConstructor @TableName("wms_ware_order_task_detail") public class WareOrderTaskDetailEntity implements Serializable { private static final long serialVersionUID = 1L; diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/WareSkuService.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/WareSkuService.java index 4d6b35e..a6758a8 100644 --- a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/WareSkuService.java +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/WareSkuService.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService; import name.lkk.common.to.es.SkuHasStockVo; import name.lkk.common.utils.PageUtils; import name.lkk.kkmall.ware.entity.WareSkuEntity; +import name.lkk.kkmall.ware.vo.WareSkuLockVo; import java.util.List; import java.util.Map; @@ -26,9 +27,14 @@ public interface WareSkuService extends IService { /** * 查询sku是否有库存 + * * @param skuIds * @return */ List getSkuHasStock(List skuIds); + + Boolean orderLockStock(WareSkuLockVo vo); + + } diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/impl/WareSkuServiceImpl.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/impl/WareSkuServiceImpl.java index 6d26719..03801d0 100644 --- a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/impl/WareSkuServiceImpl.java +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/service/impl/WareSkuServiceImpl.java @@ -3,14 +3,22 @@ package name.lkk.kkmall.ware.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import name.lkk.common.exception.NotStockException; import name.lkk.common.to.es.SkuHasStockVo; import name.lkk.common.utils.PageUtils; import name.lkk.common.utils.Query; import name.lkk.common.utils.R; import name.lkk.kkmall.ware.dao.WareSkuDao; +import name.lkk.kkmall.ware.entity.WareOrderTaskDetailEntity; +import name.lkk.kkmall.ware.entity.WareOrderTaskEntity; import name.lkk.kkmall.ware.entity.WareSkuEntity; import name.lkk.kkmall.ware.feign.ProductFeignService; +import name.lkk.kkmall.ware.service.WareOrderTaskDetailService; +import name.lkk.kkmall.ware.service.WareOrderTaskService; import name.lkk.kkmall.ware.service.WareSkuService; +import name.lkk.kkmall.ware.vo.OrderItemVo; +import name.lkk.kkmall.ware.vo.SkuWareHasStock; +import name.lkk.kkmall.ware.vo.WareSkuLockVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +36,13 @@ public class WareSkuServiceImpl extends ServiceImpl i private WareSkuDao wareSkuDao; @Autowired private ProductFeignService productFeignService; + + @Autowired + private WareOrderTaskService orderTaskService; + + @Autowired + private WareOrderTaskDetailService orderTaskDetailService; + /** * 商品库存的模糊查询 * skuId: 1 @@ -101,9 +116,69 @@ public class WareSkuServiceImpl extends ServiceImpl i // 查询当前sku的总库存量 stockVo.setSkuId(id); // 这里库存可能为null 要避免空指针异常 - stockVo.setHasStock(baseMapper.getSkuStock(id)==null?false:true); + stockVo.setHasStock(baseMapper.getSkuStock(id) == null ? false : true); return stockVo; }).collect(Collectors.toList()); } + @Transactional(rollbackFor = NotStockException.class) + @Override + public Boolean orderLockStock(WareSkuLockVo vo) { + // 当定库存之前先保存订单 以便后来消息撤回 + WareOrderTaskEntity taskEntity = new WareOrderTaskEntity(); + taskEntity.setOrderSn(vo.getOrderSn()); + orderTaskService.save(taskEntity); + // [理论上]1. 按照下单的收获地址 找到一个就近仓库, 锁定库存 + // [实际上]1. 找到每一个商品在那个一个仓库有库存 + List locks = vo.getLocks(); + List collect = locks.stream().map(item -> { + SkuWareHasStock hasStock = new SkuWareHasStock(); + Long skuId = item.getSkuId(); + hasStock.setSkuId(skuId); + // 查询这两个商品在哪有库存 + List wareIds = wareSkuDao.listWareIdHasSkuStock(skuId); + hasStock.setWareId(wareIds); + hasStock.setNum(item.getCount()); + return hasStock; + }).collect(Collectors.toList()); + + for (SkuWareHasStock hasStock : collect) { + Boolean skuStocked = true; + Long skuId = hasStock.getSkuId(); + List wareIds = hasStock.getWareId(); + if (wareIds == null || wareIds.size() == 0) { + // 没有任何仓库有这个库存 + throw new NotStockException(skuId.toString()); + } + // 如果每一个商品都锁定成功 将当前商品锁定了几件的工作单记录发送给MQ + // 如果锁定失败 前面保存的工作单信息回滚了 + for (Long wareId : wareIds) { + // 成功就返回 1 失败返回0 + Long count = wareSkuDao.lockSkuStock(skuId, wareId, hasStock.getNum()); + if (count == 1) { + // TODO 告诉MQ库存锁定成功 一个订单锁定成功 消息队列就会有一个消息 + WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity(null, skuId, "", hasStock.getNum(), taskEntity.getId(), wareId, 1); + orderTaskDetailService.save(detailEntity); +// StockLockedTo stockLockedTo = new StockLockedTo(); +// stockLockedTo.setId(taskEntity.getId()); +// StockDetailTo detailTo = new StockDetailTo(); +// BeanUtils.copyProperties(detailEntity, detailTo); +// // 防止回滚以后找不到数据 把详细信息页 +// stockLockedTo.setDetailTo(detailTo); +// +// rabbitTemplate.convertAndSend(eventExchange, routingKey ,stockLockedTo); + skuStocked = false; + break; + } + // 当前仓库锁定失败 重试下一个仓库 + } + if (skuStocked) { + // 当前商品在所有仓库都没锁柱 + throw new NotStockException(skuId.toString()); + } + } + // 3.全部锁定成功 + return true; + } + } \ No newline at end of file diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderItemVo.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderItemVo.java new file mode 100644 index 0000000..493866d --- /dev/null +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderItemVo.java @@ -0,0 +1,46 @@ +package name.lkk.kkmall.ware.vo; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + + +@Data +public class OrderItemVo { + + private Long skuId; + + /** + * 是否被选中 + */ + private Boolean check = true; + + private String title; + + private String image; + + private List skuAttr; + + /** + * 价格 + */ + private BigDecimal price; + + /** + * 数量 + */ + private Integer count; + + private BigDecimal totalPrice; + + /** + * 是否有货 + */ +// private boolean hasStock; + + /** + * 重量 + */ + private BigDecimal weight; +} diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderVo.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderVo.java new file mode 100644 index 0000000..17aba02 --- /dev/null +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/OrderVo.java @@ -0,0 +1,177 @@ +package name.lkk.kkmall.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; +} diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/SkuWareHasStock.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/SkuWareHasStock.java new file mode 100644 index 0000000..e64940d --- /dev/null +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/SkuWareHasStock.java @@ -0,0 +1,19 @@ +package name.lkk.kkmall.ware.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author: kirklin + * @date: 2021/7/13 4:57 下午 + * @description: + */ +@Data +public class SkuWareHasStock { + private Long skuId; + + private List wareId; + + private Integer num; +} diff --git a/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/WareSkuLockVo.java b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/WareSkuLockVo.java new file mode 100644 index 0000000..c87f56c --- /dev/null +++ b/kkmall-ware/src/main/java/name/lkk/kkmall/ware/vo/WareSkuLockVo.java @@ -0,0 +1,21 @@ +package name.lkk.kkmall.ware.vo; + +import lombok.Data; + +import java.util.List; + + +@Data +public class WareSkuLockVo { + + /** + * 订单号 + */ + private String orderSn; + + /** + * 要锁住的所有库存信息 + */ + private List locks; + +} diff --git a/kkmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml b/kkmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml index 34dbd0b..e423746 100644 --- a/kkmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml +++ b/kkmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml @@ -14,10 +14,33 @@ - UPDATE `wms_ware_sku` SET stock = stock + #{skuNum} WHERE sku_id = #{skuId} AND ware_id = #{wareId} + UPDATE `wms_ware_sku` + SET stock = stock + #{skuNum} + WHERE sku_id = #{skuId} + AND ware_id = #{wareId} - + + + UPDATE `wms_ware_sku` + SET stock_locked = stock_locked + #{num} + WHERE sku_id = #{skuId} + AND ware_id = #{wareId} + AND stock - stock_locked >= #{num} + + + UPDATE `wms_ware_sku` + SET stock_locked = stock_locked - #{num} + WHERE sku_id = #{skuId} + AND ware_id = #{wareId} + \ No newline at end of file