diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductMapper.java b/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductMapper.java index 01810c6..377ca01 100644 --- a/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductMapper.java +++ b/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductMapper.java @@ -5,6 +5,7 @@ import com.chaozhanggui.system.cashierservice.entity.TbProductWithBLOBs; import com.chaozhanggui.system.cashierservice.entity.vo.ShopGroupInfoVo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Component; import java.util.List; @@ -36,4 +37,7 @@ public interface TbProductMapper { void upGroupRealSalesNumber(@Param("id") String id,@Param("number") Integer number); void updateStockById(@Param("productId") String productId, @Param("num") Integer num); + + @Update("update tb_product set stock_number=stock_number-#{num} WHERE id=#{id} and stock_number-#{num} > 0") + int decrStock(@Param("id") String id, @Param("num") Integer num); } diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductSkuMapper.java b/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductSkuMapper.java index 7edb951..73a9a1d 100644 --- a/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductSkuMapper.java +++ b/src/main/java/com/chaozhanggui/system/cashierservice/dao/TbProductSkuMapper.java @@ -5,6 +5,7 @@ import com.chaozhanggui.system.cashierservice.entity.TbProductSkuWithBLOBs; import com.chaozhanggui.system.cashierservice.entity.vo.HomeVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Component; import java.util.List; @@ -39,4 +40,7 @@ public interface TbProductSkuMapper { List selectSkus(@Param("list") List productId); List selectSku(@Param("productId") String productId); -} \ No newline at end of file + + @Update("update tb_product_sku set stock_number=stock_number-#{num} WHERE id=#{id} and stock_number-#{num} > 0") + int decrStock(@Param("id") String id, @Param("num") Integer num); +} diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/redis/RedisCst.java b/src/main/java/com/chaozhanggui/system/cashierservice/redis/RedisCst.java index 8bf5631..e249a08 100644 --- a/src/main/java/com/chaozhanggui/system/cashierservice/redis/RedisCst.java +++ b/src/main/java/com/chaozhanggui/system/cashierservice/redis/RedisCst.java @@ -22,4 +22,6 @@ public class RedisCst { public static final String COUPONS_COIN_KEY = "COUPONS:COIN:KEY"; public static final String OUT_NUMBER="ORDER:NUMBER:"; + // 创建订单锁 + public static final String CREATE_ORDER_LOCK = "CREATE_ORDER_LOCK:"; } diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/service/CartService.java b/src/main/java/com/chaozhanggui/system/cashierservice/service/CartService.java index 8a5f96a..a2d5e87 100644 --- a/src/main/java/com/chaozhanggui/system/cashierservice/service/CartService.java +++ b/src/main/java/com/chaozhanggui/system/cashierservice/service/CartService.java @@ -12,12 +12,14 @@ import com.chaozhanggui.system.cashierservice.redis.RedisCst; import com.chaozhanggui.system.cashierservice.redis.RedisUtil; import com.chaozhanggui.system.cashierservice.util.DateUtils; import com.chaozhanggui.system.cashierservice.util.JSONUtil; +import com.chaozhanggui.system.cashierservice.util.LockUtils; import com.chaozhanggui.system.cashierservice.util.N; import com.chaozhanggui.system.cashierservice.wxUtil.WechatUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -68,10 +70,20 @@ public class CartService { @Autowired private TbShopInfoMapper tbShopInfoMapper; - public CartService(TbUserShopMsgMapper tbUserShopMsgMapper, WechatUtil wechatUtil, TbShopOpenIdMapper shopOpenIdMapper) { + + private final ProductService productService; + + private final TbProductMapper tbProductMapper; + + private final RedisTemplate redisTemplate; + + public CartService(TbUserShopMsgMapper tbUserShopMsgMapper, WechatUtil wechatUtil, TbShopOpenIdMapper shopOpenIdMapper, ProductService productService, TbProductMapper tbProductMapper, RedisTemplate redisTemplate) { this.tbUserShopMsgMapper = tbUserShopMsgMapper; this.wechatUtil = wechatUtil; this.shopOpenIdMapper = shopOpenIdMapper; + this.productService = productService; + this.tbProductMapper = tbProductMapper; + this.redisTemplate = redisTemplate; } public void initCart(JSONObject jsonObject) { @@ -110,6 +122,7 @@ public class CartService { /** * 加入购物车 + * * @param jsonObject 商品信息 */ // @Transactional(rollbackFor = Exception.class) @@ -276,14 +289,16 @@ public class CartService { // log.error("长链接错误 createCart{}", e.getMessage()); // } // } - public void createCart(JSONObject jsonObject) { try { + String tableId = jsonObject.getString("tableId"); String shopId = jsonObject.getString("shopId"); String productId = jsonObject.getString("productId"); String key = tableId + "-" + shopId; + TbProduct tbProduct = productMapper.selectById(Integer.valueOf(productId)); + TbProductSkuWithBLOBs tbProductSkuWithBLOBs = null; if (tbProduct == null) { JSONObject jsonObject1 = new JSONObject(); jsonObject1.put("status", "fail"); @@ -307,8 +322,8 @@ public class CartService { throw new MsgException("该商品已售罄"); } } else { - TbProductSkuWithBLOBs tbProductSkuWithBLOBs = productSkuMapper.selectByPrimaryKey(Integer.valueOf(skuId)); - if(tbProductSkuWithBLOBs.getIsPauseSale().equals(1)){//是否售罄 + tbProductSkuWithBLOBs = productSkuMapper.selectByPrimaryKey(Integer.valueOf(skuId)); + if (tbProductSkuWithBLOBs.getIsPauseSale().equals(1)) {//是否售罄 JSONObject jsonObject1 = new JSONObject(); jsonObject1.put("status", "fail"); jsonObject1.put("msg", "该商品已售罄"); @@ -325,10 +340,13 @@ public class CartService { if (redisUtil.exists(RedisCst.TABLE_CART.concat(jsonObject.getString("tableId").concat("-").concat(shopId)))) { JSONArray array = JSON.parseArray(redisUtil.getMessage(RedisCst.TABLE_CART.concat(jsonObject.getString("tableId").concat("-").concat(shopId)))); + // 未创建购物车,新增购物车 if (Objects.isNull(array) || array.isEmpty()) { if (buyNum > 0) { + TbCashierCart cashierCart = addCart(jsonObject.getString("productId"), skuId, jsonObject.getInteger("userId"), buyNum, tableId, shopId); + jsonArray.add(cashierCart); amount = amount.add(new BigDecimal(cashierCart.getNumber()).multiply(cashierCart.getSalePrice().add(cashierCart.getPackFee()))); } @@ -337,10 +355,13 @@ public class CartService { for (int i = 0; i < array.size(); i++) { JSONObject object = array.getJSONObject(i); TbCashierCart cashierCart = JSONUtil.parseJSONStr2T(object.toJSONString(), TbCashierCart.class); + + // 已添加购物车商品,加数量 if (cashierCart.getSkuId().equals(skuId)) { cashierCart.setTotalNumber(cashierCart.getTotalNumber() + buyNum); cashierCart.setNumber(cashierCart.getNumber() + buyNum); if (cashierCart.getNumber() > 0) { + cashierCart.setTotalAmount(new BigDecimal(cashierCart.getTotalNumber()).multiply(cashierCart.getSalePrice().add(cashierCart.getPackFee()))); cashierCart.setUpdatedAt(Instant.now().toEpochMilli()); cashierCartMapper.updateByPrimaryKeySelective(cashierCart); @@ -353,13 +374,16 @@ public class CartService { jsonArray.add(cashierCart); amount = amount.add(new BigDecimal(cashierCart.getTotalNumber()).multiply(cashierCart.getSalePrice().add(cashierCart.getPackFee()))); } + // 已有购物车中不存在,新增 if (flag && buyNum > 0) { + TbCashierCart cashierCart = addCart(jsonObject.getString("productId"), skuId, jsonObject.getInteger("userId"), buyNum, tableId, jsonObject.getString("shopId")); jsonArray.add(cashierCart); amount = amount.add(new BigDecimal(cashierCart.getTotalNumber()).multiply(cashierCart.getSalePrice().add(cashierCart.getPackFee()))); } } + // 未创建购物车新增 } else { if (buyNum > 0) { TbCashierCart cashierCart = addCart(jsonObject.getString("productId"), skuId, @@ -376,6 +400,7 @@ public class CartService { jsonObject1.put("data", jsonArray); jsonObject1.put("amount", amount); PushToAppChannelHandlerAdapter.getInstance().AppSendInfo(jsonObject1.toString(), jsonObject.getString("tableId").concat("-").concat(shopId), "", false); + } catch (Exception e) { log.error("长链接错误 createCart{}", e.getMessage()); } @@ -383,9 +408,10 @@ public class CartService { /** * 修改库存并根据警告线发送消息 - * @param product 商品 + * + * @param product 商品 * @param productSku sku - * @param num 库存数 + * @param num 库存数 */ private void updateProductStock(TbProduct product, TbProductSkuWithBLOBs productSku, Integer num) { String key = RedisCst.PRODUCT + product.getShopId() + ":product" + product.getId(); @@ -411,6 +437,7 @@ public class CartService { /** * 校验商品库存警戒线并通知商户 + * * @param productSku sku */ private void checkWarnLineAndSendMsg(TbProductSku productSku, TbProduct product, Integer num) { @@ -438,13 +465,13 @@ public class CartService { List shopOpenIds = shopOpenIdMapper.selectByShopId(Integer.valueOf(product.getShopId())); shopOpenIds.forEach(item -> { wechatUtil.sendStockWarnMsg(shopInfo.getShopName(), product.getName(), - product.getIsDistribute() == 1 ? String.valueOf(product.getStockNumber()-num) : String.valueOf(productSku.getStockNumber() - num), + product.getIsDistribute() == 1 ? String.valueOf(product.getStockNumber() - num) : String.valueOf(productSku.getStockNumber() - num), "耗材库存不足,请及时补充。", item.getOpenId()); }); } } - private TbCashierCart addCart(String productId, String skuId, Integer userId, Integer num, String tableId, String shopId) throws Exception { + private TbCashierCart addCart(String productId, String skuId, Integer userId, Integer num, String tableId, String shopId) throws RuntimeException { try { TbProduct product = productMapper.selectById(Integer.valueOf(productId)); String key = tableId + "-" + shopId; @@ -536,6 +563,11 @@ public class CartService { JSONObject object = array.getJSONObject(i); TbCashierCart cashierCart = JSONUtil.parseJSONStr2T(object.toJSONString(), TbCashierCart.class); TbProductSkuWithBLOBs tbProduct = productSkuMapper.selectByPrimaryKey(Integer.valueOf(cashierCart.getSkuId())); + TbProduct tbProduct1 = tbProductMapper.selectById(Integer.valueOf(tbProduct.getProductId())); + log.info("开始修改库存,商品id:{},商品名:{}", tbProduct1.getId(), tbProduct1.getName()); + // 修改库存 + productService.updateStock(tbProduct.getProductId(), tbProduct.getId(), cashierCart.getNumber(), tbProduct1.getIsDistribute() == 1); + totalAmount = totalAmount.add(cashierCart.getTotalAmount()); packAMount = packAMount.add(cashierCart.getPackFee()); originAmount = originAmount.add(cashierCart.getTotalAmount()); @@ -709,11 +741,7 @@ public class CartService { orderInfoMapper.insert(orderInfo); orderId = orderInfo.getId(); - // 发送mq消息 - JSONObject jsonObject2=new JSONObject(); - jsonObject2.put("orderId",orderInfo.getId()); - jsonObject2.put("type","create"); - producer.cons(jsonObject2.toString()); + } for (TbOrderDetail orderDetail : orderDetails) { orderDetail.setOrderId(orderId); @@ -728,6 +756,14 @@ public class CartService { cashierCartMapper.updateByPrimaryKeySelective(cashierCart); object.put("updatedAt", System.currentTimeMillis()); object.put("orderId", orderId + ""); + + // 发送mq消息 + JSONObject jsonObject2 = new JSONObject(); + jsonObject2.put("orderId", orderInfo.getId()); + jsonObject2.put("type", "create"); + jsonObject2.put("cartId", cashierCart.getId()); + log.info("开始发送mq消息,消耗库存,消息内容:{}", jsonObject2); + producer.cons(jsonObject2.toString()); } redisUtil.saveMessage(RedisCst.TABLE_CART.concat(jsonObject.getString("tableId")).concat("-").concat(shopId), array.toJSONString()); orderInfo.setDetailList(orderDetails); @@ -747,6 +783,8 @@ public class CartService { jsonObject12.put("data", new JSONArray()); PushToAppChannelHandlerAdapter.getInstance().AppSendInfo(jsonObject12.toString(), jsonObject.getString("tableId").concat("-").concat(shopId), "", false); redisUtil.saveMessage(RedisCst.ORDER_EXPIRED.concat(orderId.toString()), orderId.toString(), 60 * 16L); + + } catch (Exception e) { log.info("长链接错误 addCart{}", e.getMessage()); e.printStackTrace(); diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/service/ProductService.java b/src/main/java/com/chaozhanggui/system/cashierservice/service/ProductService.java index 6eab725..c890ee7 100644 --- a/src/main/java/com/chaozhanggui/system/cashierservice/service/ProductService.java +++ b/src/main/java/com/chaozhanggui/system/cashierservice/service/ProductService.java @@ -8,6 +8,7 @@ import com.chaozhanggui.system.cashierservice.dao.*; import com.chaozhanggui.system.cashierservice.entity.*; import com.chaozhanggui.system.cashierservice.entity.dto.HomeDto; import com.chaozhanggui.system.cashierservice.entity.vo.*; +import com.chaozhanggui.system.cashierservice.exception.MsgException; import com.chaozhanggui.system.cashierservice.sign.CodeEnum; import com.chaozhanggui.system.cashierservice.sign.Result; import com.chaozhanggui.system.cashierservice.util.*; @@ -433,4 +434,35 @@ public class ProductService { } return Result.success(CodeEnum.SUCCESS, confirmVo); } + + /** + * 库存修改 + * + * @param tbProduct 商品 + * @param tbProductSkuWithBLOBs sku + * @param buyNum 购买数量 + */ + public void updateStock(TbProduct tbProduct, TbProductSkuWithBLOBs tbProductSkuWithBLOBs, Integer buyNum) { + if (tbProduct.getIsDistribute() == 1) { + if (tbProductMapper.decrStock(String.valueOf(tbProduct.getId()), buyNum) < 1) { + throw new MsgException("库存修改失败,请稍后再试"); + } + }else { + if (tbProductSkuMapper.decrStock(String.valueOf(tbProductSkuWithBLOBs.getId()), buyNum) < 1) { + throw new MsgException("库存修改失败,请稍后再试"); + } + } + } + + public void updateStock(String id, Integer skuId, Integer buyNum, boolean isDistribute) { + if (isDistribute) { + if (tbProductMapper.decrStock(String.valueOf(id), buyNum) < 1) { + throw new MsgException("库存不足,下单失败"); + } + }else { + if (tbProductSkuMapper.decrStock(String.valueOf(skuId), buyNum) < 1) { + throw new MsgException("库存不足,下单失败"); + } + } + } } diff --git a/src/main/java/com/chaozhanggui/system/cashierservice/util/LockUtils.java b/src/main/java/com/chaozhanggui/system/cashierservice/util/LockUtils.java new file mode 100644 index 0000000..d00636a --- /dev/null +++ b/src/main/java/com/chaozhanggui/system/cashierservice/util/LockUtils.java @@ -0,0 +1,35 @@ +package com.chaozhanggui.system.cashierservice.util; + +import cn.hutool.core.lang.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Slf4j +public class LockUtils { + + public static T runFunAndCheckKey(Supplier supplier, RedisTemplate redisTemplate, String lockKey) { + try{ + // 创建线程id, 用作判断 + String clientId = UUID.randomUUID().toString(); + // 设置分布式锁 + boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS)); + int count = 0; + while (!lock) { + if (count++ > 100) { + throw new RuntimeException("系统繁忙, 稍后再试"); + } + Thread.sleep(20); + lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS)); + } + return supplier.get(); + } catch (Exception e){ + log.info("执行出错:{}", e.getMessage()); + throw new RuntimeException(e.getMessage()); + }finally{ + redisTemplate.delete(lockKey); + } + } +}