Ver código fonte

fix: 处理订单修改因事务原因导致库存不足的问题

luofeiyun 2 semanas atrás
pai
commit
20a06b97e6

+ 36 - 9
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/api/VoyageApiImpl.java

@@ -24,6 +24,7 @@ import com.yc.ship.module.product.enums.VoyageStockLogTypeEnum;
 import com.yc.ship.module.product.enums.YesOrNoEnum;
 import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.product.service.voyagestockdistribute.VoyageStockDistributeNewService;
+import com.yc.ship.module.product.utils.ProductRedisUtils;
 import com.yc.ship.module.resource.api.room.RoomApi;
 import com.yc.ship.module.trade.enums.TradeOrderStatusEnum;
 import lombok.extern.slf4j.Slf4j;
@@ -66,6 +67,9 @@ public class VoyageApiImpl implements VoyageApi {
     @Resource
     private RedissonClient redissonClient;
 
+    @Resource
+    private ProductRedisUtils productRedisUtils;
+
     @Resource
     private VoyageStockRoomUsedMapper voyageStockRoomUsedMapper;
 
@@ -124,8 +128,8 @@ public class VoyageApiImpl implements VoyageApi {
             if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {//分销商下单,当前阶段不考虑门店下单
                 //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
                 //初定扣分销商库存
-                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType());
                 preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType());
+                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType());
             }
         } finally {
             if (lock.isHeldByCurrentThread() && lock.isLocked()) {
@@ -209,6 +213,7 @@ public class VoyageApiImpl implements VoyageApi {
             stockDO.setCanSellNum(stockDO.getCanSellNum().add(totalNum)); //退回可售库存
             stockDO.setBookNum(stockDO.getBookNum().subtract(totalNum)); // 减去已预订数量
             voyageStockMapper.updateById(stockDO);//更新航次总库存
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
             //删除航次库存操作记录
             voyageStockLogMapper.deleteById(voyageStockLogDO);
         } else {
@@ -235,6 +240,7 @@ public class VoyageApiImpl implements VoyageApi {
                 item.setVirtualNum(item.getVirtualNum().add(VirtualNumMap.get(key)));
             });
             voyageStockDetailMapper.updateBatch(stockDetailList);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
             Integer stockType = detailLogList.get(0).getStockType();
             //2:还分销商库存
@@ -253,6 +259,7 @@ public class VoyageApiImpl implements VoyageApi {
                         item.setBookNum(item.getBookNum() == null ? BigDecimal.ZERO : item.getBookNum().subtract(totalNumMap.get(key)));
                     });
                     voyageStockDistributeNewMapper.updateBatch(list);
+                    productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId, list);
                 }
             }
             // 删除库存详情初定记录
@@ -288,7 +295,10 @@ public class VoyageApiImpl implements VoyageApi {
         voyageStockLogDO.setDistributorId(distributorId);
         voyageStockLogDO.setStoreId(storeId);
         // 获取航次总库存
-        VoyageStockDO stockDO = voyageStockMapper.selectById(voyageId);
+        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class, 1);
+        if(stockDO == null) {
+            stockDO = voyageStockMapper.selectById(voyageId);
+        }
         if (stockDO == null) {
             throw exception(VOYAGE_NO_STOCK);
         }
@@ -296,8 +306,11 @@ public class VoyageApiImpl implements VoyageApi {
         BigDecimal shareNum = stockDO.getShareNum();
 
         //航次库存详情
-        List<VoyageStockDetailDO> stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
-
+        //航次库存详情
+        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class, 1);
+        if(CollectionUtils.isAnyEmpty(stockDetailList)) {
+            stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
+        }
         //判断分销商库存是否存在
         VoyageStockDistributeNewDetailReqVO detailReqVO = new VoyageStockDistributeNewDetailReqVO();
         detailReqVO.setVoyageId(voyageId);
@@ -305,7 +318,10 @@ public class VoyageApiImpl implements VoyageApi {
         detailReqVO.setObjectId(distributorId);
         //先获取非共享库存(分销商库存)
         detailReqVO.setEnableShare(YesOrNoEnum.NO.getType());
-        List<VoyageStockDistributeNewRespVO> distributeNewResps = voyageStockDistributeNewService.getDetail(detailReqVO);
+        List<VoyageStockDistributeNewRespVO> distributeNewResps = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId, VoyageStockDistributeNewRespVO.class, 1);
+        if(CollectionUtils.isAnyEmpty(distributeNewResps)) {
+            distributeNewResps = voyageStockDistributeNewService.getDetail(detailReqVO);
+        }
         //订单使用房间详情
         List<OrderRoomUseDTO> orderRoomList = reqDTO.getOrderRoomList();
         Map<String, BigDecimal> roomModelFloorNumMap = orderRoomList.stream()
@@ -569,6 +585,7 @@ public class VoyageApiImpl implements VoyageApi {
             stockDO.setBookNum(stockDO.getBookNum().subtract(totalNum));
             //更新航次总库存
             voyageStockMapper.updateById(stockDO);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
             //删除航次库存操作记录
             voyageStockLogMapper.deleteById(voyageStockLogDO);
         } else {
@@ -599,6 +616,7 @@ public class VoyageApiImpl implements VoyageApi {
                 }
             });
             voyageStockDetailMapper.updateBatch(stockDetailList);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
 
             //2:还分销商库存
@@ -626,6 +644,7 @@ public class VoyageApiImpl implements VoyageApi {
                         }
                     });
                     voyageStockDistributeNewMapper.updateBatch(list);
+                    productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId, list);
                 }
             }
             // 删除库存详情初定记录
@@ -667,7 +686,10 @@ public class VoyageApiImpl implements VoyageApi {
         voyageStockLogDO.setDistributorId(distributorId);
         voyageStockLogDO.setStoreId(storeId);
         // 获取航次总库存
-        VoyageStockDO stockDO = voyageStockMapper.selectById(voyageId);
+        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class, 1);
+        if(stockDO == null) {
+            stockDO = voyageStockMapper.selectById(voyageId);
+        }
         if (stockDO == null) {
             throw exception(VOYAGE_NO_STOCK);
         }
@@ -675,8 +697,10 @@ public class VoyageApiImpl implements VoyageApi {
         BigDecimal shareNum = stockDO.getShareNum();
 
         //航次库存详情
-        List<VoyageStockDetailDO> stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
-
+        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class, 1);
+        if(CollectionUtils.isAnyEmpty(stockDetailList)) {
+            stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
+        }
         //判断分销商库存是否存在
         VoyageStockDistributeNewDetailReqVO detailReqVO = new VoyageStockDistributeNewDetailReqVO();
         detailReqVO.setVoyageId(voyageId);
@@ -684,7 +708,10 @@ public class VoyageApiImpl implements VoyageApi {
         detailReqVO.setObjectId(distributorId);
         //先获取非共享库存(分销商库存)
         detailReqVO.setEnableShare(YesOrNoEnum.NO.getType());
-        List<VoyageStockDistributeNewRespVO> distributeNewRespList = voyageStockDistributeNewService.getDetail(detailReqVO);
+        List<VoyageStockDistributeNewRespVO> distributeNewRespList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId, VoyageStockDistributeNewRespVO.class, 1);
+        if(CollectionUtils.isAnyEmpty(distributeNewRespList)) {
+            distributeNewRespList = voyageStockDistributeNewService.getDetail(detailReqVO);
+        }
         //订单使用房间详情
         List<OrderRoomUseDTO> orderRoomList = reqDTO.getOrderRoomList();
         Map<String, BigDecimal> roomModelFloorNumMap = orderRoomList.stream()

+ 4 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/controller/admin/voyagestockdetail/vo/VoyageStockDetailSaveReqVO.java

@@ -53,5 +53,9 @@ public class VoyageStockDetailSaveReqVO {
     @ExcelProperty("实房间数")
     private BigDecimal realTotalNum;
 
+    @Schema(description = "共享房间数")
+    @ExcelProperty("共享房间数")
+    private BigDecimal shareNum;
+
 
 }

+ 16 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/framework/lock/ProductRedisKeyConstants.java

@@ -8,5 +8,21 @@ public interface ProductRedisKeyConstants {
      * 给航次库存加锁的key前缀 %s为航次ID
      */
     String STOCK_REDIS_KEY_PREFIX = "product:stock:%s";
+    /**
+     * 航次总库存缓存,由于退还和扣操作在同一个事务中,所以将退还后的库存设置到redis中,方便后续查询
+     */
+    String STOCK_TOTAL_REDIS_KEY_PREFIX = "stock:total:stock:%s";
+    /**
+     * 航次库存详情缓存,由于退还和扣操作在同一个事务中,所以将退还后的库存设置到redis中,方便后续查询
+     */
+    String STOCK_DETAIL_REDIS_KEY_PREFIX = "stock:detail:stock:%s";
+    /**
+     * 航次分销商库存详情缓存,由于退还和扣操作在同一个事务中,所以将退还后的库存设置到redis中,方便后续查询
+     */
+    String STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX = "stock:distributor:stock:%s";
+
+
+
+
 
 }

+ 4 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyage/VoyageServiceImpl.java

@@ -150,6 +150,7 @@ public class VoyageServiceImpl implements VoyageService {
                 voyageStockDO.setVirtualNum(BigDecimal.ZERO);
                 voyageStockDO.setOversoldNum(BigDecimal.ZERO);
                 voyageStockDO.setSurplusNum(sum);
+                voyageStockDO.setShareNum(BigDecimal.ZERO);
                 voyageStockDO.setVoyageId(voyageId);
                 voyageStockMapper.insert(voyageStockDO);
                 //添加库存详情
@@ -170,6 +171,7 @@ public class VoyageServiceImpl implements VoyageService {
                     stockDetailDO.setOversoldNum(BigDecimal.ZERO);
                     stockDetailDO.setVirtualNum(BigDecimal.ZERO);
                     stockDetailDO.setSurplusNum(BigDecimal.valueOf(item.getNum()));
+                    stockDetailDO.setShareNum(BigDecimal.ZERO);
                     stockDetailDOS.add(stockDetailDO);
                 });
                 voyageStockDetailMapper.insertBatch(stockDetailDOS);
@@ -206,6 +208,7 @@ public class VoyageServiceImpl implements VoyageService {
             voyageStockDO.setVirtualNum(zero);
             voyageStockDO.setOversoldNum(zero);
             voyageStockDO.setSurplusNum(sum);
+            voyageStockDO.setShareNum(zero);
             voyageStockDO.setVoyageId(item.getId());
             voyageStockDOList.add(voyageStockDO);
             list.stream().forEach(roomModelFloorNumDTO -> {
@@ -225,6 +228,7 @@ public class VoyageServiceImpl implements VoyageService {
                 stockDetailDO.setOversoldNum(zero);
                 stockDetailDO.setVirtualNum(zero);
                 stockDetailDO.setSurplusNum(num);
+                stockDetailDO.setShareNum(zero);
                 stockDetailDOS.add(stockDetailDO);
             });
         });

+ 196 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/utils/ProductRedisUtils.java

@@ -0,0 +1,196 @@
+
+package com.yc.ship.module.product.utils;
+
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.formula.functions.T;
+import org.springframework.data.redis.core.*;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Redis工具类
+ *
+ * @author Mark sunlightcs@gmail.com
+ */
+@Component
+@Slf4j
+public class ProductRedisUtils {
+
+    @Resource
+    private RedisTemplate redisTemplate;
+    @Resource(name="redisTemplate")
+    private ValueOperations<String, String> valueOperations;
+    @Resource(name="redisTemplate")
+    private ListOperations<String, String> listOperations;
+    @Resource(name="redisTemplate")
+    private SetOperations<String, Object> setOperations;
+    @Resource(name="redisTemplate")
+    private ZSetOperations<String, Object> zSetOperations;
+    /**  默认过期时长,单位:秒 */
+    public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
+    /**  不设置过期时长 */
+    public final static long NOT_EXPIRE = -1;
+
+    public void set(String key, Object value, long expire){
+        valueOperations.set(key, toJson(value));
+        if(expire != NOT_EXPIRE){
+            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
+        }
+    }
+
+    public void set(String key, Object value){
+        set(key, value, DEFAULT_EXPIRE);
+    }
+
+    public <T> T get(String key, Class<T> clazz, long expire) {
+        String value = valueOperations.get(key);
+        if(expire != NOT_EXPIRE){
+            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
+        }
+        return value == null ? null : fromJson(value, clazz);
+    }
+
+    public <T> List<T> getList(String key, Class<T> clazz, long expire) {
+        String value = valueOperations.get(key);
+        if(expire != NOT_EXPIRE){
+            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
+        }
+        return value == null ? null : fromJsonList(value, clazz);
+    }
+
+    public <T> T get(String key, Class<T> clazz) {
+        return get(key, clazz, NOT_EXPIRE);
+    }
+
+    public String get(String key, long expire) {
+        String value = valueOperations.get(key);
+        if(expire != NOT_EXPIRE){
+            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
+        }
+        return value;
+    }
+
+    public Integer getInt(String key) {
+        Integer value = (Integer)redisTemplate.opsForValue().get(key);
+        if(value==null){
+            return null;
+        }
+        return value;
+    }
+
+    /**
+     * 递增
+     * @param key 键
+     * @param delta  递增因子
+     * @return
+     */
+    public long incr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     * @param key 键
+     * @param delta  递减因子
+     * @return
+     */
+    public long decr(String key, long delta){
+        if(delta<0){
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().decrement(key, delta);
+    }
+
+    /**
+     * 手动设置Key过期
+     * @param key 键
+     * @param timeout 时长
+     * @param timeUnit 时长单独
+     */
+    public void  expire(String key,long timeout,TimeUnit timeUnit){
+        redisTemplate.expire(key,timeout,timeUnit);
+    }
+
+    public String get(String key) {
+        return get(key, NOT_EXPIRE);
+    }
+
+    public void delete(String key) {
+        redisTemplate.delete(key);
+    }
+
+    /**
+     * Object转成JSON数据
+     */
+    private String toJson(Object object){
+        if(object instanceof Integer || object instanceof Long || object instanceof Float ||
+                object instanceof Double || object instanceof Boolean || object instanceof String){
+            return String.valueOf(object);
+        }
+        return JSONUtil.toJsonStr(object);
+    }
+
+    /**
+     * JSON数据,转成Object
+     */
+    private <T> T fromJson(String json, Class<T> clazz){
+        return JSONUtil.toBean(json, clazz);
+    }
+
+    /**
+     * JSON数据,转成Object
+     */
+    private <T> List<T> fromJsonList(String json, Class<T> clazz){
+        return JSONUtil.toList(json, clazz);
+    }
+
+
+    public String getAndSet(final String key, String value) {
+        String oldValue = null;
+        try {
+            oldValue = valueOperations.getAndSet(key, value);
+            redisTemplate.expire(key, 120, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return oldValue;
+    }
+
+    public Long rightPush(final String key, String value) {
+        Long oldValue = null;
+        try {
+            oldValue = listOperations.rightPush(key, value);
+            redisTemplate.expire(key, 60*60*24, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return oldValue;
+    }
+
+    public List<String> range(final String key, Integer readNum) {
+        List<String> oldValue = null;
+        try {
+            oldValue = listOperations.range(key, 0,readNum);
+            redisTemplate.expire(key, 60*60*24, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return oldValue;
+    }
+
+    public void trim(final String key, Integer delSize) {
+        try {
+            listOperations.trim(key, delSize,-1L);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 1 - 1
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml

@@ -2477,7 +2477,7 @@
         order by num desc
     </select>
     <select id="queryOrderByLast" resultType="com.yc.ship.module.trade.dal.dataobject.order.TradeOrderDO">
-        SELECT  * FROM trade_order WHERE order_no = #{orderNo} and deleted=1   ORDER BY update_time DESC limit 1
+        SELECT  * FROM trade_order WHERE order_no = #{orderNo}  ORDER BY update_time DESC limit 1
     </select>
 
     <select id="queryOrderById" resultType="com.yc.ship.module.trade.dal.dataobject.order.TradeOrderDO">