diff --git a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/domain/OmsOrder.java b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/domain/OmsOrder.java index 70fe8d834..cba61b1fc 100644 --- a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/domain/OmsOrder.java +++ b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/domain/OmsOrder.java @@ -51,7 +51,7 @@ public class OmsOrder extends BaseEntity { /** * 会员id */ - private Long userId; + private Long memberId; /** * 使用的优惠券 */ diff --git a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/dto/OrderSubmitDTO.java b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/dto/OrderSubmitDTO.java index 8b529e4e2..f1286e773 100644 --- a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/dto/OrderSubmitDTO.java +++ b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/dto/OrderSubmitDTO.java @@ -5,6 +5,7 @@ import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import java.math.BigDecimal; import java.util.List; /** @@ -21,9 +22,8 @@ public class OrderSubmitDTO { private List orderItems; - private Long payAmount; - - private String couponId; + // 验价前台传值 + private Long totalPrice; @NotBlank(message = "请选择收货地址") private String addressId; @@ -31,4 +31,9 @@ public class OrderSubmitDTO { @Size(max = 500, message = "订单备注长度不能超过500") private String remark; + + private Long payAmount; + + private String couponId; + } diff --git a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/vo/CartVO.java b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/vo/CartVO.java index 2df868507..11abda25d 100644 --- a/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/vo/CartVO.java +++ b/mall-oms/oms-api/src/main/java/com/youlai/mall/oms/pojo/vo/CartVO.java @@ -28,7 +28,7 @@ public class CartVO implements Serializable { private Integer count; - private Long price; + private Long price; // 加入购物车价格,因会变动,不能作为订单计算因子,订单验价时需重新获取商品价格即可 private Long coupon; diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/constant/OmsConstants.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/constant/OmsConstants.java index 8c72a4cb2..d195becd5 100644 --- a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/constant/OmsConstants.java +++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/constant/OmsConstants.java @@ -12,4 +12,16 @@ public interface OmsConstants { String BUSINESS_NO_PREFIX = "businessno:"; + + /** + * 释放锁lua脚本 + */ + String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; + + /** + * 释放锁成功返回值 + */ + Long RELEASE_LOCK_SUCCESS_RESULT = 1L; + + } diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/listener/RabbitMQListener.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/listener/RabbitMQListener.java index 3da8bbf82..5ac08ee10 100644 --- a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/listener/RabbitMQListener.java +++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/listener/RabbitMQListener.java @@ -5,7 +5,7 @@ import com.youlai.mall.oms.pojo.domain.OmsOrderItem; import com.youlai.mall.oms.service.IOrderItemService; import com.youlai.mall.oms.service.IOrderService; import com.youlai.mall.pms.api.app.PmsSkuFeignService; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; @@ -32,7 +32,7 @@ public class RabbitMQListener { IOrderItemService orderItemService; - PmsSkuFeignService inventoryFeignService; + PmsSkuFeignService skuFeignService; RabbitTemplate rabbitTemplate; @@ -47,12 +47,12 @@ public class RabbitMQListener { // 如果关单成功,发送消息释放库存 // rabbitTemplate.convertAndSend("product_event_change", "stock:unlock", orderSn); List orderItems = orderItemService.getByOrderId(orderId); - List inventoryList = orderItems.stream().map(orderItem -> InventoryDTO.builder() + List stockLick = orderItems.stream().map(orderItem -> SkuLockDTO.builder() .skuId(orderItem.getSkuId()) .count(orderItem.getSkuQuantity()) .build()) .collect(Collectors.toList()); - inventoryFeignService.unlockStock(inventoryList); + skuFeignService.unlockStock(stockLick); } else { // 如果关单失败,则订单可能已经被处理,直接手动ACK确认消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderPayServiceImpl.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderPayServiceImpl.java index 0659d98bd..8bfebf0ce 100644 --- a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderPayServiceImpl.java +++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderPayServiceImpl.java @@ -14,7 +14,7 @@ import com.youlai.mall.oms.service.IOrderItemService; import com.youlai.mall.oms.service.IOrderPayService; import com.youlai.mall.oms.service.IOrderService; import com.youlai.mall.pms.api.app.PmsSkuFeignService; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import com.youlai.mall.ums.api.UmsMemberFeignService; import io.seata.spring.annotation.GlobalTransactional; import lombok.AllArgsConstructor; @@ -36,7 +36,7 @@ public class OrderPayServiceImpl extends ServiceImpl orderItems = orderItemService.getByOrderId(orderId); - List inventoryList = orderItems.stream().map(orderItem -> InventoryDTO.builder() + List stockLick = orderItems.stream().map(orderItem -> SkuLockDTO.builder() .skuId(orderItem.getSkuId()) .count(orderItem.getSkuQuantity()) .build()) .collect(Collectors.toList()); - inventoryFeignService.deductStock(inventoryList); + skuFeignService.deductStock(stockLick); // 添加订单支付记录 OmsOrderPay orderPay = OmsOrderPay.builder() diff --git a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderServiceImpl.java b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderServiceImpl.java index 9bc54ae13..43a4a3822 100644 --- a/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderServiceImpl.java +++ b/mall-oms/oms-boot/src/main/java/com/youlai/mall/oms/service/impl/OrderServiceImpl.java @@ -14,7 +14,6 @@ import com.youlai.common.result.ResultCode; import com.youlai.common.web.exception.BizException; import com.youlai.common.web.util.BeanMapperUtils; import com.youlai.common.web.util.RequestUtils; -import com.youlai.mall.oms.constant.OmsConstants; import com.youlai.mall.oms.enums.OrderStatusEnum; import com.youlai.mall.oms.enums.OrderTypeEnum; import com.youlai.mall.oms.mapper.OrderMapper; @@ -28,7 +27,7 @@ import com.youlai.mall.oms.pojo.vo.*; import com.youlai.mall.oms.service.*; import com.youlai.mall.pms.api.app.PmsSkuFeignService; import com.youlai.mall.pms.pojo.domain.PmsSku; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import com.youlai.mall.pms.pojo.dto.SkuDTO; import com.youlai.mall.ums.api.UmsAddressFeignService; import com.youlai.mall.ums.api.UmsMemberFeignService; @@ -38,20 +37,20 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.data.redis.core.RedisTemplate; +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.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.Collectors; +import static com.youlai.mall.oms.constant.OmsConstants.*; + @AllArgsConstructor @Slf4j @Service @@ -77,7 +76,7 @@ public class OrderServiceImpl extends ServiceImpl impleme private RabbitTemplate rabbitTemplate; - private RedisTemplate redisTemplate; + private StringRedisTemplate redisTemplate; private ThreadPoolExecutor threadPoolExecutor; @@ -126,7 +125,7 @@ public class OrderServiceImpl extends ServiceImpl impleme CompletableFuture orderTokenCompletableFuture = CompletableFuture.runAsync(() -> { String orderToken = IdUtil.randomUUID(); orderConfirmVO.setOrderToken(orderToken); - redisTemplate.opsForValue().set(OmsConstants.ORDER_TOKEN_PREFIX + orderToken, orderToken); + redisTemplate.opsForValue().set(ORDER_TOKEN_PREFIX + orderToken, orderToken); }, threadPoolExecutor); CompletableFuture.allOf(orderItemsCompletableFuture, addressesCompletableFuture, orderTokenCompletableFuture); @@ -137,178 +136,76 @@ public class OrderServiceImpl extends ServiceImpl impleme @GlobalTransactional public OrderSubmitVO submit(OrderSubmitDTO submitDTO) { - submitDTO. + // 订单重复提交校验 + String orderToken = submitDTO.getOrderToken(); + DefaultRedisScript redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class); + Long result = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken); - - - - log.info("开始创建订单:{}", submitInfoDTO); - threadLocal.set(submitInfoDTO); - RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); - - OrderBO orderBO = new OrderBO(); - CompletableFuture orderFuture; - CompletableFuture orderItemFuture; - CompletableFuture orderDeliveryFuture; - - // 创建订单任务 - { - orderFuture = CompletableFuture.runAsync(() -> { - RequestContextHolder.setRequestAttributes(attributes); - threadLocal.set(submitInfoDTO); - OrderSubmitDTO submitInfo = threadLocal.get(); - log.info("订单提交信息:{}", submitInfo); - OmsOrder order = new OmsOrder(); - order.setOrderSn(IdWorker.getTimeId()) - .setRemark(submitInfo.getRemark()) - .setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode()) - .setSourceType(OrderTypeEnum.APP.getCode()) - .setUserId(RequestUtils.getUserId()); - log.debug("完善后的订单信息:{}", order.toString()); - orderBO.setOrder(order); - - }, executor); + if (ObjectUtil.equals(result, RELEASE_LOCK_SUCCESS_RESULT)) { + throw new BizException("订单不可重复提交"); } - // 创建订单商品任务 - { - orderItemFuture = CompletableFuture.runAsync(() -> { - RequestContextHolder.setRequestAttributes(attributes); - threadLocal.set(submitInfoDTO); - OrderSubmitDTO submitInfo = threadLocal.get(); - List orderItems; - if (submitInfoDTO.getSkuId() != null) { // 直接下单 - orderItems = new ArrayList<>(); - orderItems.add(OmsOrderItem.builder().skuId(submitInfo.getSkuId()).skuQuantity(submitInfo.getSkuNum()).build()); - } else { // 购物车下单 - CartVO cart = cartService.getCart(); - orderItems = cart.getItems().stream().map(cartItem -> OmsOrderItem.builder().skuId(cartItem.getSkuId()) - .skuQuantity(cartItem.getCount()) - .build() - ).collect(Collectors.toList()); - } - - List skuIds = orderItems.stream().map(item -> item.getSkuId()) - .collect(Collectors.toList()); - - List skuList = inventoryFeignService.listBySkuIds(skuIds).getData(); - if (CollectionUtil.isEmpty(skuList)) { - throw new BizException("订单商品库存为空"); - } - for (OmsOrderItem orderItem : orderItems) { - skuList.stream().filter(sku -> sku.getId().equals(orderItem.getSkuId())).findFirst() - .ifPresent(skuItem -> { - BeanUtil.copyProperties(skuItem, orderItem); - orderItem.setSkuPrice(skuItem.getPrice()); - orderItem.setSkuTotalPrice(orderItem.getSkuPrice() * orderItem.getSkuQuantity()); - }); - } - orderBO.setOrderItems(orderItems); - }, executor); + List orderItems = submitDTO.getOrderItems(); + if (CollectionUtil.isEmpty(orderItems)) { + throw new BizException("请选择商品再提交"); } - // 创建发货信息任务 - { - orderDeliveryFuture = CompletableFuture.runAsync(() -> { - RequestContextHolder.setRequestAttributes(attributes); - threadLocal.set(submitInfoDTO); - String addressId = threadLocal.get().getAddressId(); - UmsAddressDTO userAddress = memberFeignService.getAddressById(addressId).getData(); - if (userAddress == null) { - throw new BizException("会员地址不存在"); - } - OmsOrderDelivery orderDelivery = OmsOrderDelivery.builder() - .receiverName(userAddress.getName()) - .receiverPhone(userAddress.getMobile()) - .receiverPostCode(userAddress.getZipCode()) - .receiverProvince(userAddress.getProvince()) - .receiverCity(userAddress.getCity()) - .receiverRegion(userAddress.getArea()) - .receiverDetailAddress(userAddress.getAddress()) - .build(); - - orderBO.setOrderDelivery(orderDelivery); - }, executor); - } - - CompletableFuture future = CompletableFuture.allOf(orderFuture, orderItemFuture, orderDeliveryFuture); - future.get(); - // 订单验价 - { - OmsOrder order = orderBO.getOrder(); - List orderItems = orderBO.getOrderItems(); - - log.info("计算订单价格:order:{},orderItems:{}", order, orderItems); - - if (order == null || CollectionUtil.isEmpty(orderItems)) { - throw new BizException("订单或订单商品列表为空,订单创建失败"); + Long currentTotalPrice = orderItems.stream().map(item -> { + PmsSku sku = skuFeignService.getSkuById(item.getSkuId()).getData(); + if (sku != null) { + return sku.getPrice() * item.getCount(); } + return 0l; + }).reduce(0l, Long::sum); - Long totalAmount = orderItems.stream().mapToLong(OmsOrderItem::getSkuTotalPrice).sum(); - int totalQuantity = orderItems.stream().mapToInt(OmsOrderItem::getSkuQuantity).sum(); - Long payAmount = totalAmount; - if (order.getCouponAmount() != null) { - payAmount -= order.getCouponAmount(); - } - if (order.getFreightAmount() != null) { - payAmount -= order.getFreightAmount(); - } - - - OrderSubmitDTO orderSubmitInfo = threadLocal.get(); - int compare = Long.compare(orderSubmitInfo.getPayAmount().longValue(), payAmount.longValue()); - if (compare != 0) { - throw new BizException("订单价格变化,请重新提交"); - } - - order.setTotalAmount(totalAmount); - order.setTotalQuantity(totalQuantity); - order.setPayAmount(payAmount); + if (currentTotalPrice.compareTo(submitDTO.getTotalPrice()) != 0) { + throw new BizException("页面已过期,请重新刷新页面再提交"); } - // 锁定库存 - { - List orderItems = orderBO.getOrderItems(); - List items = orderItems.stream().map(orderItem -> InventoryDTO.builder() - .skuId(orderItem.getSkuId()) - .count(orderItem.getSkuQuantity()) - .build()) - .collect(Collectors.toList()); + // 校验库存是否足够和锁库存 + List skuLockList = orderItems.stream() + .map(item -> SkuLockDTO.builder().skuId(item.getSkuId()) + .count(item.getCount()) + .orderToken(orderToken) + .build()) + .collect(Collectors.toList()); - Result result = inventoryFeignService.lockStock(items); - if (!StrUtil.equals(result.getCode(), ResultCode.SUCCESS.getCode())) { - throw new BizException("下单失败,锁定库存错误"); - } + Result lockResult = skuFeignService.lockStock(skuLockList); + + if (!Result.success().getCode().equals(lockResult.getCode())) { + throw new BizException(Result.failed().getMsg()); } // 保存订单 - OmsOrder order = orderBO.getOrder(); + OmsOrder order = new OmsOrder(); + order.setOrderSn(IdWorker.getTimeId()) + .setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode()) + .setSourceType(OrderTypeEnum.APP.getCode()) + .setMemberId(RequestUtils.getUserId()) + .setRemark(submitDTO.getRemark()); this.save(order); - Long orderId = order.getId(); // 保存订单商品 - List orderItems = orderBO.getOrderItems(); - orderItems.forEach(item -> item.setOrderId(orderId)); - orderItemService.saveBatch(orderItems); - - // 保存发货信息 - OmsOrderDelivery orderDelivery = orderBO.getOrderDelivery(); - orderDelivery.setOrderId(orderId); - orderDeliveryService.save(orderDelivery); - - // 删除购物车中已购买的商品 - if (ObjectUtil.isNull(submitInfoDTO.getSkuId())) { - cartService.deleteSelectedItem(); - } + List orderItemList = orderItems.stream().map(item -> OmsOrderItem.builder() + .orderId(order.getId()) + .skuId(item.getSkuId()) + .skuPrice(item.getPrice()) + .skuPic(item.getSkuPic()) + .skuQuantity(item.getCount()) + .build()).collect(Collectors.toList()); + orderItemService.saveBatch(orderItemList); // 将订单放入延时队列,超时未支付系统自动关单 - rabbitTemplate.convertAndSend("order-exchange", "order:create", orderId); + rabbitTemplate.convertAndSend("order-exchange", "order:create", orderToken); + + // 记录发货 TODO + + OrderSubmitVO submitVO = new OrderSubmitVO(); + submitVO.setId(order.getId()); + submitVO.setOrderSn(order.getOrderSn()); + return submitVO; - OrderSubmitVO result = new OrderSubmitVO(); - result.setId(orderId); - result.setOrderSn(order.getOrderSn()); - return result; } @@ -388,7 +285,7 @@ public class OrderServiceImpl extends ServiceImpl impleme Long userId = RequestUtils.getUserId(); OmsOrder order = this.getOne(new LambdaQueryWrapper() .eq(OmsOrder::getId, id) - .eq(OmsOrder::getUserId, userId)); + .eq(OmsOrder::getMemberId, userId)); if (order == null) { throw new BizException("订单不存在,订单ID非法"); } @@ -396,24 +293,5 @@ public class OrderServiceImpl extends ServiceImpl impleme } - /** - * 获取订单商品 1. 直接购买 2. 购物车结算 - */ - private List getOrderItems(Long skuId, Integer count) { - if (skuId != null) { // 直接购买 - OrderItemVO itemVO = OrderItemVO.builder() - .skuId(skuId) - .count(count) - .build(); - return Arrays.asList(itemVO); - } - // 购物车结算,从购物车获取商品 - CartVO cart = cartService.getCart(); - List items = cart.getItems().stream() - .filter(CartItemVO::isChecked) - .map(item -> OrderItemVO.builder().skuId(item.getSkuId()).count(item.getCount()).build()) - .collect(Collectors.toList()); - return items; - } } diff --git a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/InventoryFeignService.java b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/SkuFeignService.java similarity index 81% rename from mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/InventoryFeignService.java rename to mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/SkuFeignService.java index 0a49f9ca2..385d06fa5 100644 --- a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/InventoryFeignService.java +++ b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/admin/SkuFeignService.java @@ -6,8 +6,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; -@FeignClient(value = "mall-pms",contextId = "AdminInventoryFeignService") -public interface InventoryFeignService { +@FeignClient(value = "mall-pms",contextId = "AdminskuFeignService") +public interface SkuFeignService { /** * 扣减库存 diff --git a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/app/PmsSkuFeignService.java b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/app/PmsSkuFeignService.java index a81bd4c8b..9a8738fff 100644 --- a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/app/PmsSkuFeignService.java +++ b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/api/app/PmsSkuFeignService.java @@ -3,7 +3,7 @@ package com.youlai.mall.pms.api.app; import com.youlai.common.result.Result; import com.youlai.mall.pms.pojo.domain.PmsSku; import com.youlai.mall.pms.pojo.dto.SkuDTO; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; @@ -28,17 +28,17 @@ public interface PmsSkuFeignService { * 锁定库存 */ @PutMapping("/api.app/v1/skus/batch/lock_stock") - Result lockStock(@RequestBody List list); + Result lockStock(@RequestBody List list); /** * 解锁库存 */ @PutMapping("/api.app/v1/skus/batch/unlock_stock") - Result unlockStock(@RequestBody List list); + Result unlockStock(@RequestBody List list); @PutMapping("/api.app/v1/skus/batch/deduct_stock") - Result deductStock(@RequestBody List list); + Result deductStock(@RequestBody List list); } diff --git a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/InventoryDTO.java b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/SkuLockDTO.java similarity index 70% rename from mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/InventoryDTO.java rename to mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/SkuLockDTO.java index 2b4b38892..3b99a28cd 100644 --- a/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/InventoryDTO.java +++ b/mall-pms/pms-api/src/main/java/com/youlai/mall/pms/pojo/dto/SkuLockDTO.java @@ -1,6 +1,5 @@ package com.youlai.mall.pms.pojo.dto; -import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -15,12 +14,14 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor -public class InventoryDTO { +public class SkuLockDTO { - @ApiModelProperty("库存ID") private Long skuId; - @ApiModelProperty("数量") private Integer count; + private String orderToken; + + private Boolean locked; + } diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/common/constant/PmsConstants.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/common/constant/PmsConstants.java index 1dc981f32..cecfbc546 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/common/constant/PmsConstants.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/common/constant/PmsConstants.java @@ -6,6 +6,6 @@ package com.youlai.mall.pms.common.constant; */ public interface PmsConstants { - String PRODUCT_INVENTORY_PREFIX = "pms:stock:"; + String PMS_STOCK_LOCK_PREFIX = "pms:stock:lock:"; } diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/admin/SkuController.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/admin/SkuController.java index b2fefe09a..1ae222700 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/admin/SkuController.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/admin/SkuController.java @@ -54,7 +54,7 @@ public class SkuController { @PutMapping("/{id}/stock") public Result updateStock(@PathVariable Long id, @RequestParam Integer num) { PmsSku sku = iPmsSkuService.getById(id); - sku.setInventory(sku.getInventory() + num); + sku.setStock(sku.getStock() + num); boolean result = iPmsSkuService.updateById(sku); return Result.judge(result); } diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/app/SkuController.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/app/SkuController.java index 994fb8b7f..c62c5425b 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/app/SkuController.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/controller/app/SkuController.java @@ -3,7 +3,7 @@ package com.youlai.mall.pms.controller.app; import com.youlai.common.result.Result; import com.youlai.mall.pms.pojo.domain.PmsSku; import com.youlai.mall.pms.pojo.dto.SkuDTO; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import com.youlai.mall.pms.service.IPmsSkuService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; @@ -41,7 +41,7 @@ public class SkuController { @ApiOperation(value = "锁定库存", httpMethod = "PUT") @ApiImplicitParam(name = "list", value = "锁定库存", required = true, paramType = "body", dataType = "InventoryNumDTO") @PutMapping("/batch/lock_stock") - public Result lockStock(@RequestBody List list) { + public Result lockStock(@RequestBody List list) { boolean result = iPmsSkuService.lockStock(list); return Result.judge(result); } @@ -50,7 +50,7 @@ public class SkuController { @ApiOperation(value = "解锁库存", httpMethod = "PUT") @ApiImplicitParam(name = "list", value = "释放库存", required = true, paramType = "body", dataType = "InventoryNumDTO") @PutMapping("/batch/unlock_stock") - public Result unlockStock(@RequestBody List list) { + public Result unlockStock(@RequestBody List list) { boolean result = iPmsSkuService.unlockStock(list); return Result.judge(result); } @@ -58,7 +58,7 @@ public class SkuController { @ApiOperation(value = "扣减库存", httpMethod = "PUT") @ApiImplicitParam(name = "list", value = "释放库存", required = true, paramType = "body", dataType = "InventoryNumDTO") @PutMapping("/batch/deduct_stock") - public Result deductStock(@RequestBody List list) { + public Result deductStock(@RequestBody List list) { boolean result = iPmsSkuService.deductStock(list); return Result.judge(result); } diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/InventoryListener.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/PmsListener.java similarity index 87% rename from mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/InventoryListener.java rename to mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/PmsListener.java index f9181c8b0..0b3e8ba0d 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/InventoryListener.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/listener/PmsListener.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Component; * @date 2021-03-16 */ @Component -public class InventoryListener { +public class PmsListener { /** diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/IPmsSkuService.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/IPmsSkuService.java index 3d13466cb..dce1cd010 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/IPmsSkuService.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/IPmsSkuService.java @@ -3,7 +3,7 @@ package com.youlai.mall.pms.service; import com.baomidou.mybatisplus.extension.service.IService; import com.youlai.mall.pms.pojo.domain.PmsSku; import com.youlai.mall.pms.pojo.dto.SkuDTO; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import java.util.List; @@ -14,14 +14,14 @@ public interface IPmsSkuService extends IService { * @param list * @return 库存锁定结果 */ - boolean lockStock(List list); + boolean lockStock(List list); /** * 解锁库存 * @param list * @return 解锁库存结果 */ - boolean unlockStock(List list); + boolean unlockStock(List list); /** @@ -39,5 +39,5 @@ public interface IPmsSkuService extends IService { */ List listBySkuIds(List ids); - boolean deductStock(List list); + boolean deductStock(List list); } diff --git a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/impl/PmsSkuServiceImpl.java b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/impl/PmsSkuServiceImpl.java index 351bb1f08..967fd3b4c 100644 --- a/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/impl/PmsSkuServiceImpl.java +++ b/mall-pms/pms-boot/src/main/java/com/youlai/mall/pms/service/impl/PmsSkuServiceImpl.java @@ -1,6 +1,8 @@ package com.youlai.mall.pms.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -9,15 +11,18 @@ import com.youlai.mall.pms.common.constant.PmsConstants; import com.youlai.mall.pms.mapper.PmsSkuMapper; import com.youlai.mall.pms.pojo.domain.PmsSku; import com.youlai.mall.pms.pojo.dto.SkuDTO; -import com.youlai.mall.pms.pojo.dto.InventoryDTO; +import com.youlai.mall.pms.pojo.dto.SkuLockDTO; import com.youlai.mall.pms.service.IPmsSkuService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.stream.Collectors; + +import static com.youlai.mall.pms.common.constant.PmsConstants.PMS_STOCK_LOCK_PREFIX; @Service @Slf4j @@ -27,31 +32,52 @@ public class PmsSkuServiceImpl extends ServiceImpl impleme private RedisTemplate redisTemplate; - @Override - @Transactional(rollbackFor = Exception.class) - public boolean lockStock(List inventories) { - log.info("锁定库存: {}", inventories); + private StringRedisTemplate stringRedisTemplate; - inventories.forEach(item -> { + @Override + public boolean lockStock(List skuLockList) { + + if (CollectionUtil.isNotEmpty(skuLockList)) { + throw new BizException("锁定商品为空"); + } + + // 锁定商品 + skuLockList.forEach(item -> { boolean result = this.update(new LambdaUpdateWrapper() + .setSql("locked_stock = locked_stock + " + item.getCount()) .eq(PmsSku::getId, item.getSkuId()) - .apply("stock >= locked_inventory + {0}", item.getCount()) - .setSql("locked_inventory = locked_inventory + " + item.getCount()) + .apply("stock >= locked_stock + {0}", item.getCount()) ); - if (!result) { - throw new BizException("锁定库存失败,库存ID:" + item.getSkuId() + ",数量:" + item.getCount()); + if (result) { + item.setLocked(true); + } else { + item.setLocked(false); } }); + // 锁定失败的商品集合 + List unlockSkus = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList()); + if (CollectionUtil.isNotEmpty(unlockSkus)) { + // 恢复已被锁定的库存 + List lockSkus = skuLockList.stream().filter(SkuLockDTO::getLocked).collect(Collectors.toList()); + this.unlockStock(lockSkus); + + List ids = unlockSkus.stream().map(SkuLockDTO::getSkuId).collect(Collectors.toList()); + throw new BizException("商品" + ids.toString() + "库存不足"); + } + + // 将锁定的商品保存至Redis中 + String orderToken = skuLockList.get(0).getOrderToken(); + stringRedisTemplate.opsForValue().set(PMS_STOCK_LOCK_PREFIX+orderToken, JSONUtil.toJsonStr(skuLockList)); return true; } @Override - public boolean unlockStock(List inventories) { - inventories.forEach(item -> { + public boolean unlockStock(List skuList) { + skuList.forEach(item -> { boolean result = this.update(new LambdaUpdateWrapper() .eq(PmsSku::getId, item.getSkuId()) - .setSql("locked_inventory = locked_inventory - " + item.getCount()) + .setSql("locked_stock = locked_stock - " + item.getCount()) ); if (!result) { throw new BizException("解锁库存失败,库存ID:" + item.getSkuId() + ",数量:" + item.getCount()); @@ -62,11 +88,11 @@ public class PmsSkuServiceImpl extends ServiceImpl impleme @Override - public boolean deductStock(List inventories) { + public boolean deductStock(List inventories) { inventories.forEach(item -> { boolean result = this.update(new LambdaUpdateWrapper() .eq(PmsSku::getId, item.getSkuId()) - .setSql("locked_inventory = locked_inventory - " + item.getCount()) + .setSql("locked_stock = locked_stock - " + item.getCount()) .setSql("stock = stock - " + item.getCount()) ); if (!result) { @@ -89,7 +115,7 @@ public class PmsSkuServiceImpl extends ServiceImpl impleme public Integer getStockById(Long id) { Integer stock = 0; // 读->缓存 - Object cacheVal = redisTemplate.opsForValue().get(PmsConstants.PRODUCT_INVENTORY_PREFIX + id); + Object cacheVal = redisTemplate.opsForValue().get(PMS_STOCK_LOCK_PREFIX + id); if (cacheVal != null) { stock = Convert.toInt(cacheVal); return stock; @@ -98,12 +124,12 @@ public class PmsSkuServiceImpl extends ServiceImpl impleme // 读->数据库 PmsSku pmsSku = this.getOne(new LambdaQueryWrapper() .eq(PmsSku::getId, id) - .select(PmsSku::getInventory)); + .select(PmsSku::getStock)); if (pmsSku != null) { - stock = pmsSku.getInventory(); + stock = pmsSku.getStock(); // 写->缓存 - redisTemplate.opsForValue().set(PmsConstants.PRODUCT_INVENTORY_PREFIX + id, stock); + redisTemplate.opsForValue().set(PmsConstants.PMS_STOCK_LOCK_PREFIX + id, stock); } return stock;