Quellcode durchsuchen

fix: 处理库存问题,将锁库存操作放到最外层的事务中去处理,同时必须等事务全部提交后才解锁,事务隔离级别需要使用读已提交

luofeiyun vor 2 Wochen
Ursprung
Commit
b9fc4da7a0

+ 85 - 196
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/api/VoyageApiImpl.java

@@ -22,19 +22,16 @@ import com.yc.ship.module.product.dal.mysql.voyagestockdetail.VoyageStockDetailL
 import com.yc.ship.module.product.dal.mysql.voyagestockdetail.VoyageStockDetailMapper;
 import com.yc.ship.module.product.dal.mysql.voyagestockdistribute.VoyageStockDistributeNewMapper;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
-import com.yc.ship.module.product.enums.ProductStockStaticsName;
 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.dal.redis.RedisKeyConstants;
 import com.yc.ship.module.trade.enums.TradeOrderStatusEnum;
 import lombok.extern.slf4j.Slf4j;
 import me.zhyd.oauth.utils.UuidUtils;
-import org.redisson.api.RLock;
-import org.redisson.api.RedissonClient;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
@@ -45,7 +42,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -71,9 +67,6 @@ public class VoyageApiImpl implements VoyageApi {
     @Resource
     private VoyageStockDistributeNewService voyageStockDistributeNewService;
 
-    @Resource
-    private RedissonClient redissonClient;
-
     @Resource
     private ProductRedisUtils productRedisUtils;
 
@@ -87,82 +80,58 @@ public class VoyageApiImpl implements VoyageApi {
     private VoyageStockDetailLogMapper voyageStockDetailLogMapper;
 
     @Override
-    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
+    @Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
     public void preReduceStock(ReduceStockReqDTO reqDTO) {
         log.info("开始初定订单扣库存:请求参数:{}", JSONObject.toJSONString(reqDTO));
         //TODO: 当前阶段不考虑门店库存
         log.info("开始初定订单扣库存:订单ID:{}old订单ID:{}", reqDTO.getOrderId(), reqDTO.getOldOrderId());
 
-        Long voyageId = reqDTO.getVoyageId();
-        //现在给整个航次的库存加锁
-        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
-        RLock lock = redissonClient.getLock(lockKey);
-        //用来控制redis缓存
-        try {
-            lock.lock(60, TimeUnit.SECONDS);
-            //判断是分销商还是门店下单 1:分销商,2:门店
-            Integer type = reqDTO.getType();
-            //分销商下单,当前阶段不考虑门店下单
-            if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
-                //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
-                //初定扣分销商库存
-                //先返还初定扣的库存,使用修改前一步订单的 ID
-                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                log.info("先还该订单的库存-orderId: {}", reqDTO.getOrderId());
-                if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
-                    log.info("如果是修改就还修改前的订单的库存-oldOrderId: {}", reqDTO.getOldOrderId());
-                    preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                }
-                //再去扣初定的库存
-                preReduceStockDistributor(reqDTO);
-            }
-        } finally {
-            if (lock.isHeldByCurrentThread() && lock.isLocked()) {
-                lock.unlock();
+//        // 锁放到最外层去
+        String uuid = UuidUtils.getUUID();
+        //判断是分销商还是门店下单 1:分销商,2:门店
+        Integer type = reqDTO.getType();
+        //分销商下单,当前阶段不考虑门店下单
+        if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
+            //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
+            //初定扣分销商库存
+            //先返还初定扣的库存,使用修改前一步订单的 ID
+            preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
+            log.info("先还该订单的库存-orderId: {}", reqDTO.getOrderId());
+            if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
+                log.info("如果是修改就还修改前的订单的库存-oldOrderId: {}", reqDTO.getOldOrderId());
+                preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
             }
+            //再去扣初定的库存
+            preReduceStockDistributor(reqDTO, uuid);
         }
     }
 
     @Override
-    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
+    @Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
     public void preCancelReduceStock(ReduceStockReqDTO reqDTO) {
         //TODO: 当前阶段不考虑门店库存
         log.info("开始初定订单还库存:请求参数:{}", JSONObject.toJSONString(reqDTO));
-        Long voyageId = reqDTO.getVoyageId();
-        //现在给整个航次的库存加锁
-        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
-        RLock lock = redissonClient.getLock(lockKey);
-        try {
-            lock.lock(60, TimeUnit.SECONDS);
-            //判断是分销商还是门店下单 1:分销商,2:门店
-            Integer type = reqDTO.getType();
-            //分销商下单,当前阶段不考虑门店下单
-            if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
-                //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
-                //初定扣分销商库存
-                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
-                    preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                }
-            }
-        } finally {
-            if (lock.isHeldByCurrentThread() && lock.isLocked()) {
-                lock.unlock();
+        String uuid = UuidUtils.getUUID();
+        //判断是分销商还是门店下单 1:分销商,2:门店
+        Integer type = reqDTO.getType();
+        //分销商下单,当前阶段不考虑门店下单
+        if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
+            //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
+            //初定扣分销商库存
+            preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
+            if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
+                preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
             }
         }
     }
 
     @Override
-    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
+    @Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
     public void reduceStock(ReduceStockReqDTO reqDTO) {
         //TODO: 当前阶段不考虑门店库存
         log.info("开始确定订单扣库存:请求参数:{}", JSONObject.toJSONString(reqDTO));
-        Long voyageId = reqDTO.getVoyageId();
-        //现在给整个航次的库存加锁
-        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
-        RLock lock = redissonClient.getLock(lockKey);
         try {
-            lock.lock(60, TimeUnit.SECONDS);
+            String uuid = UuidUtils.getUUID();
             //判断是分销商还是门店下单 1:分销商,2:门店
             Integer type = reqDTO.getType();
             //分销商下单,当前阶段不考虑门店下单
@@ -170,87 +139,71 @@ public class VoyageApiImpl implements VoyageApi {
                 //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
                 //初定扣分销商库存
                 //先返还初定扣的库存
-                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
+                preCancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
                 if(reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
                     if(reqDTO.getOldOrderStatus() != null && reqDTO.getOldOrderStatus().equals(TradeOrderStatusEnum.YD.getStatus())) {
-                        preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
+                        preCancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
                     }else {
-                        cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
+                        cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
                     }
                 }
                 //再去扣确定的库存
-                reduceStockDistributor(reqDTO);
+                reduceStockDistributor(reqDTO, uuid);
             }
         } catch (Exception e) {
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-            log.error("开始确定订单扣库存异常:{}", e.getMessage());
-            throw e;
-        } finally {
-            if (lock.isHeldByCurrentThread() && lock.isLocked()) {
-                lock.unlock();
+            log.error("开始确定订单扣库存异常", e);
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
             }
-
+            throw new RuntimeException("确定订单扣库存失败:" + e.getMessage(), e);
         }
     }
 
     @Override
-    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
+    @Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
     public void cancelReduceStock(ReduceStockReqDTO reqDTO) {
         log.info("开始确定订单还库存:请求参数:{}", JSONObject.toJSONString(reqDTO));
         //TODO: 当前阶段不考虑门店库存
-        Long voyageId = reqDTO.getVoyageId();
-        //现在给整个航次的库存加锁
-        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
-        RLock lock = redissonClient.getLock(lockKey);
-        try {
-            lock.lock(60, TimeUnit.SECONDS);
-            //判断是分销商还是门店下单 1:分销商,2:门店
-            Integer type = reqDTO.getType();
-            //分销商下单,当前阶段不考虑门店下单
-            if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
-                //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
-                //初定扣分销商库存
-                cancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
-                    cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
-                }
-            }
-        } finally {
-            if (lock.isHeldByCurrentThread() && lock.isLocked()) {
-                lock.unlock();
+        String uuid = UuidUtils.getUUID();
+        //判断是分销商还是门店下单 1:分销商,2:门店
+        Integer type = reqDTO.getType();
+        //分销商下单,当前阶段不考虑门店下单
+        if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
+            //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
+            //初定扣分销商库存
+            cancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
+            if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
+                cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
             }
         }
     }
 
     @Override
-    @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
+    @Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
     public void cancelReduceStockNew(ReduceStockReqDTO reqDTO) {
         log.error("开始确定订单还库存:请求参数:{}", JSONObject.toJSONString(reqDTO));
         //TODO: 当前阶段不考虑门店库存
-        Long voyageId = reqDTO.getVoyageId();
-        //现在给整个航次的库存加锁
-        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
-        RLock lock = redissonClient.getLock(lockKey);
         try {
-            lock.lock(60, TimeUnit.SECONDS);
+            String uuid = UuidUtils.getUUID();
             //判断是分销商还是门店下单 1:分销商,2:门店
             Integer type = reqDTO.getType();
             //分销商下单,当前阶段不考虑门店下单
             if (Objects.equals(type, DistributorOrStoreEnum.DISTRIBUTOR.getValue())) {
                 //分销商下单只扣分销商库存,默认是OTA,默认OTC只能门店下单
                 //初定扣分销商库存
-                cancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
+                cancelReduceStockDistributor(reqDTO.getOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
                 if (reqDTO.getOldOrderId() != null && !reqDTO.getOldOrderId().equals(reqDTO.getOrderId())) {
-                    cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList());
+                    cancelReduceStockDistributor(reqDTO.getOldOrderId(), reqDTO.getType(),reqDTO.getOrderRoomList(), uuid);
                 }
             }
         } catch (Exception e) {
-            log.error("取消订单还库存异常:{}", e.getMessage());
-            e.printStackTrace();
-        }finally {
-            if (lock.isHeldByCurrentThread() && lock.isLocked()) {
-                lock.unlock();
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            log.error("取消订单还库存异常", e);
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
             }
+            throw new RuntimeException("取消订单还库存失败:" + e.getMessage(), e);
         }
     }
 
@@ -258,7 +211,7 @@ public class VoyageApiImpl implements VoyageApi {
      * 确定状态时取消订单还库存
      * roomUseDTOList 需要退的房间
      */
-    private void cancelReduceStockDistributor(Long orderId, Integer type,List<OrderRoomUseDTO> roomUseDTOList) {
+    private void cancelReduceStockDistributor(Long orderId, Integer type,List<OrderRoomUseDTO> roomUseDTOList, String uuid) {
         if (orderId == null) {
             return;
         }
@@ -271,12 +224,8 @@ public class VoyageApiImpl implements VoyageApi {
             voyageStockRoomUsedMapper.deleteByOrderId(orderId);
             Long voyageId = voyageStockLogDO.getVoyageId();
             log.info("开始确定订单还库存:航次ID:{}", voyageId);
-            // 获取航次总库存, 先查缓存,缓存没有再查数据库
-            VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class);
-            if (stockDO == null) {
-                log.info("开始确定订单还库存:缓存没有总库存,开始查询数据库");
-                stockDO = voyageStockMapper.selectById(voyageId);
-            }
+            // 获取航次总库存
+            VoyageStockDO stockDO = voyageStockMapper.selectById(voyageId);
             log.info("开始确定订单还库存:退还前总库存:{}", JSONObject.toJSONString(stockDO));
             //还航次总库存
             BigDecimal totalNum = voyageStockLogDO.getVirtualNum().add(voyageStockLogDO.getRealNum());
@@ -297,7 +246,7 @@ public class VoyageApiImpl implements VoyageApi {
             log.info("开始确定订单还库存:退还后总库存:{}", JSONObject.toJSONString(stockDO));
             //更新航次总库存
             voyageStockMapper.updateById(stockDO);
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, stockDO);
             //删除航次库存操作记录
             voyageStockLogMapper.deleteById(voyageStockLogDO);
         } else {
@@ -310,11 +259,8 @@ public class VoyageApiImpl implements VoyageApi {
         List<VoyageStockDetailLogDO> detailLogList = voyageStockDetailLogMapper.selectListByOrder(orderId);
         if (detailLogList != null && !detailLogList.isEmpty()) {
             Integer stockType = detailLogList.get(0).getStockType();
-            //退还航次库存详情, 先查缓存,缓存没有再查数据库
-            List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class);
-            if (CollectionUtils.isAnyEmpty(stockDetailList)) {
-                stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
-            }
+            //退还航次库存详情
+            List<VoyageStockDetailDO> stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
             log.info("开始确定订单还库存:退还前库存详情:{}", JSONObject.toJSONString(stockDetailList));
             Map<String, BigDecimal> realNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), VoyageStockDetailLogDO::getRealNum);
             Map<String, BigDecimal> virtualNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), VoyageStockDetailLogDO::getVirtualNum);
@@ -322,9 +268,6 @@ public class VoyageApiImpl implements VoyageApi {
             Map<String, BigDecimal> totalNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), item -> item.getRealNum().add(item.getVirtualNum()));
 
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 String key = item.getRoomModelId() + "_" + item.getFloor();
 
                 BigDecimal totalNum = totalNumMap.get(key);
@@ -354,7 +297,7 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("开始确定订单还库存:退还后库存详情:{}", JSONObject.toJSONString(stockDetailList));
             voyageStockDetailMapper.updateBatch(stockDetailList);
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, stockDetailList);
 
 
             //2:还分销商库存
@@ -364,11 +307,7 @@ public class VoyageApiImpl implements VoyageApi {
                 detailReqVO.setVoyageId(voyageId);
                 detailReqVO.setType(type);
                 detailReqVO.setDistributorId(distributorId);
-                // 先查缓存,缓存没有再查数据库
-                List<VoyageStockDistributeNewRespVO> distributeNewResp = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, VoyageStockDistributeNewRespVO.class);
-                if (CollectionUtils.isAnyEmpty(distributeNewResp)) {
-                    distributeNewResp = voyageStockDistributeNewService.getDetail(detailReqVO);
-                }
+                List<VoyageStockDistributeNewRespVO> distributeNewResp = voyageStockDistributeNewService.getDetail(detailReqVO);
                 List<VoyageStockDistributeNewDO> list = BeanUtils.toBean(distributeNewResp, VoyageStockDistributeNewDO.class);
                 log.info("开始确定订单还库存:退还前分销商库存:{}", JSONObject.toJSONString(list));
                 if (list != null && !list.isEmpty()) {
@@ -383,7 +322,7 @@ public class VoyageApiImpl implements VoyageApi {
                     });
                     log.info("开始确定订单还库存:退还后分销商库存:{}", JSONObject.toJSONString(list));
                     voyageStockDistributeNewMapper.updateBatch(list);
-                    productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, list);
+                    productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId + "_" + uuid, list);
                 }
             }
             // 删除库存详情初定记录
@@ -395,7 +334,7 @@ public class VoyageApiImpl implements VoyageApi {
     /**
      * 确定时扣库存,有限扣实际库存,不足再扣虚拟库存
      */
-    private void reduceStockDistributor(ReduceStockReqDTO reqDTO) {
+    private void reduceStockDistributor(ReduceStockReqDTO reqDTO, String uuid) {
         //航次库存操作日志
         VoyageStockLogDO voyageStockLogDO = new VoyageStockLogDO();
         //航次库存详情操作日志
@@ -424,7 +363,7 @@ public class VoyageApiImpl implements VoyageApi {
         voyageStockLogDO.setDistributorId(distributorId);
         voyageStockLogDO.setStoreId(storeId);
         // 获取航次总库存
-        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class);
+        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, VoyageStockDO.class, 1);
         if (stockDO == null) {
             stockDO = voyageStockMapper.selectById(voyageId);
         }
@@ -438,7 +377,7 @@ public class VoyageApiImpl implements VoyageApi {
 
         //航次库存详情
         //航次库存详情
-        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class);
+        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, VoyageStockDetailDO.class, 1);
         if (CollectionUtils.isAnyEmpty(stockDetailList)) {
             stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
         }
@@ -450,7 +389,7 @@ public class VoyageApiImpl implements VoyageApi {
         detailReqVO.setDistributorId(distributorId);
         //先获取非共享库存(分销商库存)
         detailReqVO.setEnableShare(YesOrNoEnum.NO.getType());
-        List<VoyageStockDistributeNewRespVO> distributeNewResps = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, VoyageStockDistributeNewRespVO.class);
+        List<VoyageStockDistributeNewRespVO> distributeNewResps = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId + "_" + uuid, VoyageStockDistributeNewRespVO.class, 1);
         if (CollectionUtils.isAnyEmpty(distributeNewResps)) {
             distributeNewResps = voyageStockDistributeNewService.getDetail(detailReqVO);
         }
@@ -479,9 +418,6 @@ public class VoyageApiImpl implements VoyageApi {
             // 判断库存是否充足
 
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 BigDecimal detailShareNum = item.getShareNum();
                 BigDecimal useNum = roomModelFloorNumMap.get(item.getRoomModelId() + "_" + item.getFloor());
                 if (useNum == null) {
@@ -501,9 +437,6 @@ public class VoyageApiImpl implements VoyageApi {
             //使用的虚拟库存
             List<BigDecimal> useVirtualNum = new ArrayList<>();
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 BigDecimal useNum = roomModelFloorNumMap.get(item.getRoomModelId() + "_" + item.getFloor());
                 if (useNum == null) {
                     useNum = BigDecimal.ZERO;
@@ -556,8 +489,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("开始确定订单扣库存-分销商未分配库存,扣共享库存-扣减后航次库存详情:{}", JSONObject.toJSONString(stockDetailList));
             voyageStockDetailMapper.updateBatch(stockDetailList);
-            // 设置redis缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
             // 扣总库存
             // 减可售房间数
@@ -592,8 +523,6 @@ public class VoyageApiImpl implements VoyageApi {
             voyageStockLogDO.setShareNum(totalUseNum);
             log.info("开始确定订单扣库存-分销商未分配库存,扣共享库存-扣减后航次总库存:{}", JSONObject.toJSONString(stockDO));
             voyageStockMapper.updateById(stockDO);
-            // 设置redis缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
         } else {
             log.info("开始确定订单扣库存-分销商已分配库存,扣分销商库存");
             //2分销商库存
@@ -634,8 +563,6 @@ public class VoyageApiImpl implements VoyageApi {
             log.info("开始确定订单扣库存-分销商已分配库存,扣分销商库存-扣减后分销商库存:{}", JSONObject.toJSONString(list));
 
             voyageStockDistributeNewMapper.updateBatch(list);
-            // 添加库存详情
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, list);
 
             // 库存充足,扣库存详情
             //超卖库存
@@ -645,9 +572,6 @@ public class VoyageApiImpl implements VoyageApi {
             //使用的虚拟库存
             List<BigDecimal> useVirtualNum = new ArrayList<>();
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 BigDecimal useNum = roomModelFloorNumMap.get(item.getRoomModelId() + "_" + item.getFloor());
                 if (useNum == null) {
                     useNum = BigDecimal.ZERO;
@@ -697,8 +621,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("开始确定订单扣库存-分销商已分配库存,扣分销商库存-扣减后航次库存详情:{}", JSONObject.toJSONString(stockDetailList));
             voyageStockDetailMapper.updateBatch(stockDetailList);
-            // 设置 redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
             // 扣总库存
             // 减可售房间数
@@ -734,8 +656,6 @@ public class VoyageApiImpl implements VoyageApi {
             voyageStockLogDO.setShareNum(BigDecimal.ZERO);
             log.info("开始确定订单扣库存-分销商已分配库存,扣分销商库存-扣减后航次总库存:{}", JSONObject.toJSONString(stockDO));
             voyageStockMapper.updateById(stockDO);
-            // 设置 redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
         }
         // 添加房间使用记录
         List<VoyageStockRoomUsedDO> roomUsedList = new ArrayList<>();
@@ -774,7 +694,7 @@ public class VoyageApiImpl implements VoyageApi {
     /**
      * 初定取消还分销商库存
      */
-    private void preCancelReduceStockDistributor(Long orderId, Integer type,List<OrderRoomUseDTO> orderRoomUseDTOList) {
+    private void preCancelReduceStockDistributor(Long orderId, Integer type,List<OrderRoomUseDTO> orderRoomUseDTOList, String uuid) {
         if (orderId == null) {
             return;
         }
@@ -787,11 +707,8 @@ public class VoyageApiImpl implements VoyageApi {
             //删除已使用房间记录
             voyageStockRoomUsedMapper.deleteByOrderId(orderId);
             Long voyageId = voyageStockLogDO.getVoyageId();
-            // 获取航次总库存,先从redis获取,如果redis没有,则从数据库获取
-            VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class);
-            if (stockDO == null) {
-                stockDO = voyageStockMapper.selectById(voyageId);
-            }
+            // 获取航次总库存
+            VoyageStockDO stockDO = voyageStockMapper.selectById(voyageId);
             log.info("初定还库存操作-当前航次:{}",stockDO.getVoyageName());
             log.info("初定还库存操作-当前航次退回库存前库存:{}",JSONObject.toJSONString(stockDO));
             //还航次总库存
@@ -813,7 +730,7 @@ public class VoyageApiImpl implements VoyageApi {
             log.info("初定还库存操作-当前航次退回库存后库存:{}",JSONObject.toJSONString(stockDO));
             //更新航次总库存
             voyageStockMapper.updateById(stockDO);
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, stockDO);
             //删除航次库存操作记录
             voyageStockLogMapper.deleteById(voyageStockLogDO);
         } else {
@@ -830,20 +747,14 @@ public class VoyageApiImpl implements VoyageApi {
         }
         Integer stockType = detailLogList.get(0).getStockType();
         log.info("进入初定还库存详情操作,使用库存的记录-detailLogList:{}", JSONArray.toJSONString(detailLogList));
-        //退还航次库存详情, 先从redis获取,如果redis没有,则从数据库获取
-        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class);
-        if (CollectionUtils.isAnyEmpty(stockDetailList)) {
-            stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
-        }
+        //退还航次库存详情
+        List<VoyageStockDetailDO> stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
         Map<String, BigDecimal> realNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), VoyageStockDetailLogDO::getRealNum);
         Map<String, BigDecimal> virtualNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), VoyageStockDetailLogDO::getVirtualNum);
 //            Map<String, BigDecimal> shareNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), VoyageStockDetailLogDO::getShareNum);
         Map<String, BigDecimal> totalNumMap = CollectionUtils.convertMap(detailLogList, item -> item.getRoomModelId() + "_" + item.getFloor(), item -> item.getRealNum().add(item.getVirtualNum()));
         log.info("进入初定还库存详情操作,退还前库存详情-stockDetailList:{}", JSONArray.toJSONString(stockDetailList));
         stockDetailList.forEach(item -> {
-            if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                return;
-            }
             String key = item.getRoomModelId() + "_" + item.getFloor();
             BigDecimal totalNum = MapUtils.getBigDecimal(totalNumMap, key);
             BigDecimal realNum = MapUtils.getBigDecimal(realNumMap, key);
@@ -859,7 +770,7 @@ public class VoyageApiImpl implements VoyageApi {
         });
         log.info("进入初定还库存详情操作,退还后库存详情-stockDetailList:{}", JSONArray.toJSONString(stockDetailList));
         voyageStockDetailMapper.updateBatch(stockDetailList);
-        productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
+        productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, stockDetailList);
 
 
         //2:还分销商库存
@@ -869,10 +780,7 @@ public class VoyageApiImpl implements VoyageApi {
             detailReqVO.setVoyageId(voyageId);
             detailReqVO.setType(type);
             detailReqVO.setDistributorId(distributorId);
-            List<VoyageStockDistributeNewRespVO> distributeNewRespList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, VoyageStockDistributeNewRespVO.class);
-            if (CollectionUtils.isAnyEmpty(distributeNewRespList)) {
-                distributeNewRespList = voyageStockDistributeNewService.getDetail(detailReqVO);
-            }
+            List<VoyageStockDistributeNewRespVO> distributeNewRespList = voyageStockDistributeNewService.getDetail(detailReqVO);
             List<VoyageStockDistributeNewDO> list = BeanUtils.toBean(distributeNewRespList, VoyageStockDistributeNewDO.class);
             log.info("进入初定还分销商库存详情操作,分销商库存还之前详情-distributeNewRespList:{}", JSONArray.toJSONString(distributeNewRespList));
             if (list != null && !list.isEmpty()) {
@@ -893,7 +801,7 @@ public class VoyageApiImpl implements VoyageApi {
                 });
                 log.info("进入初定还分销商库存详情操作,分销商库存还之后详情-distributeNewRespList:{}", JSONArray.toJSONString(distributeNewRespList));
                 voyageStockDistributeNewMapper.updateBatch(list);
-                productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, list);
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId + "_" + uuid, list);
             }
         }
         // 删除库存详情初定记录
@@ -905,7 +813,7 @@ public class VoyageApiImpl implements VoyageApi {
     /**
      * 初定扣分销商库存
      */
-    private void preReduceStockDistributor(ReduceStockReqDTO reqDTO) {
+    private void preReduceStockDistributor(ReduceStockReqDTO reqDTO, String uuid) {
         log.info("进入初定扣库存操作-orderId:{}", reqDTO.getOrderId());
         //航次库存操作日志
         VoyageStockLogDO voyageStockLogDO = new VoyageStockLogDO();
@@ -935,7 +843,7 @@ public class VoyageApiImpl implements VoyageApi {
         voyageStockLogDO.setDistributorId(distributorId);
         voyageStockLogDO.setStoreId(storeId);
         // 获取航次总库存
-        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, VoyageStockDO.class);
+        VoyageStockDO stockDO = productRedisUtils.get(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, VoyageStockDO.class, 1);
         if (stockDO == null) {
             stockDO = voyageStockMapper.selectById(voyageId);
         }
@@ -947,7 +855,7 @@ public class VoyageApiImpl implements VoyageApi {
         BigDecimal shareNum = stockDO.getShareNum();
 
         //航次库存详情
-        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, VoyageStockDetailDO.class);
+        List<VoyageStockDetailDO> stockDetailList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId + "_" + uuid, VoyageStockDetailDO.class, 1);
         if (CollectionUtils.isAnyEmpty(stockDetailList)) {
             stockDetailList = voyageStockDetailMapper.selectListByVoyageId(voyageId);
         }
@@ -959,7 +867,7 @@ public class VoyageApiImpl implements VoyageApi {
         detailReqVO.setDistributorId(distributorId);
         //先获取非共享库存(分销商库存)
         detailReqVO.setEnableShare(YesOrNoEnum.NO.getType());
-        List<VoyageStockDistributeNewRespVO> distributeNewRespList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, VoyageStockDistributeNewRespVO.class);
+        List<VoyageStockDistributeNewRespVO> distributeNewRespList = productRedisUtils.getList(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId + "_" + uuid, VoyageStockDistributeNewRespVO.class, 1);
         if (CollectionUtils.isAnyEmpty(distributeNewRespList)) {
             distributeNewRespList = voyageStockDistributeNewService.getDetail(detailReqVO);
         }
@@ -990,9 +898,6 @@ public class VoyageApiImpl implements VoyageApi {
             //使用的虚拟库存
             List<BigDecimal> useVirtualNum = new ArrayList<>();
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 //这个地方使用共享库存,不使用可销售数
 //                BigDecimal canSellNum = item.getCanSellNum();
                 BigDecimal detailShareNum = item.getShareNum();
@@ -1008,9 +913,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             // 库存充足,扣库存详情
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 BigDecimal useNum = roomModelFloorNumMap.get(item.getRoomModelId() + "_" + item.getFloor());
                 if (useNum == null) {
                     useNum = BigDecimal.ZERO;
@@ -1063,8 +965,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("进入初定扣库存操作-分销商未分配库存,扣减共享库存-扣减后航次库存详情:{}",JSONObject.toJSONString(stockDetailList));
             voyageStockDetailMapper.updateBatch(stockDetailList);
-            // 设置redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
             // 扣总库存
             // 减可售房间数
@@ -1096,8 +996,6 @@ public class VoyageApiImpl implements VoyageApi {
             voyageStockLogDO.setShareNum(totalUseNum);
             log.info("进入初定扣库存操作-分销商未分配库存,扣减共享库存-扣减后航次总库存:{}",JSONObject.toJSONString(stockDO));
             voyageStockMapper.updateById(stockDO);
-            // 设置redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
         } else {
             log.info("进入初定扣库存操作-分销商已分配库存,扣减分销商库存");
             //2分销商库存
@@ -1136,8 +1034,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("进入初定扣库存操作-分销商已分配库存,扣减分销商库存-扣减后分销商航次库存详情:{}",JSONObject.toJSONString(list));
             voyageStockDistributeNewMapper.updateBatch(list);
-            // 设置redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, list);
 
             // 库存充足,扣库存详情
             //使用的实际库存
@@ -1145,9 +1041,6 @@ public class VoyageApiImpl implements VoyageApi {
             //使用的虚拟库存
             List<BigDecimal> useVirtualNum = new ArrayList<>();
             stockDetailList.forEach(item -> {
-                if(Objects.equals(item.getRoomModelName(), "合计") || Objects.equals(item.getRoomModelName(), "内舱房")) {
-                    return;
-                }
                 BigDecimal useNum = roomModelFloorNumMap.get(item.getRoomModelId() + "_" + item.getFloor());
                 if (useNum == null) {
                     useNum = BigDecimal.ZERO;
@@ -1196,8 +1089,6 @@ public class VoyageApiImpl implements VoyageApi {
             });
             log.info("进入初定扣库存操作-分销商已分配库存,扣减分销商库存-扣减后航次库存详情:{}",JSONObject.toJSONString(stockDetailList));
             voyageStockDetailMapper.updateBatch(stockDetailList);
-            // 设置redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
 
             // 扣总库存
             // 减可售房间数
@@ -1230,8 +1121,6 @@ public class VoyageApiImpl implements VoyageApi {
             voyageStockLogDO.setShareNum(BigDecimal.ZERO);
             log.info("进入初定扣库存操作-分销商已分配库存,扣减分销商库存-扣减后航次总库存:{}",JSONObject.toJSONString(stockDO));
             voyageStockMapper.updateById(stockDO);
-            // 设置redis 缓存
-            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
         }
         // 添加房间使用记录
         List<VoyageStockRoomUsedDO> roomUsedList = new ArrayList<>();

+ 112 - 35
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/impl/TradeOrderServiceImpl.java

@@ -29,6 +29,7 @@ import com.yc.ship.module.product.api.ProductApi;
 import com.yc.ship.module.product.api.dto.*;
 import com.yc.ship.module.product.enums.ProductTypeEnum;
 import com.yc.ship.module.product.enums.SpecTypeEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.product.service.voyage.VoyageService;
 import com.yc.ship.module.resource.api.ship.ShipApi;
 import com.yc.ship.module.resource.dal.dataobject.route.ResourceRouteDO;
@@ -70,8 +71,14 @@ import com.yc.ship.module.trade.utils.BeanUtils;
 import com.yc.ship.module.trade.utils.TradeUUCodeUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -116,6 +123,8 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     @Resource
     private ProductApi productApi;
     @Resource
+    private RedissonClient redissonClient;
+    @Resource
     private OtaDistributorApi otaDistributorApi;
     @Resource
     private ThirdGateSystemService thirdGateSystemService;
@@ -162,6 +171,37 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     @Resource
     private DistributorCategoryMapper distributorCategoryMapper;
 
+    private boolean registerUnlockAfterTransaction(RLock lock, String lockKey) {
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            log.warn("当前无事务同步,航次锁将在 finally 中释放,lockKey:{}", lockKey);
+            return false;
+        }
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCompletion(int status) {
+                log.info("订单事务完成,释放航次锁,lockKey:{},status:{}", lockKey, status);
+                unlockStockLock(lock, lockKey);
+            }
+        });
+        return true;
+    }
+
+    private void unlockStockLock(RLock lock, String lockKey) {
+        if (lock == null) {
+            return;
+        }
+        try {
+            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.info("航次锁释放完成,lockKey:{}", lockKey);
+            } else {
+                log.warn("跳过航次锁释放,当前线程未持有锁,lockKey:{}", lockKey);
+            }
+        } catch (Exception e) {
+            log.error("航次锁释放异常,lockKey:{}", lockKey, e);
+        }
+    }
+
     @Override
     public PageResult<TradeOrderRespVO> getPlatOrderPage(TradeOrderPageReqVO pageReqVO) {
         IPage<TradeOrderRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
@@ -239,12 +279,23 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_CREATE)
     public CommonResult<TradeOrderRespDTO> createOrder(TradeOrderCreateReqDTO orderCreateReqDTO) {
         TradeOrderBO tradeOrderBO = null;
+        RLock stockLock = null;
+        String stockLockKey = null;
+        boolean unlockRegistered = false;
         try {
             //1.构建订单
             tradeOrderBO = buildCreateOrder(orderCreateReqDTO);
+            Long voyageId = tradeOrderBO.getTradeOrderDO().getVoyageId();
+            if (voyageId != null) {
+                stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
+                stockLock = redissonClient.getLock(stockLockKey);
+                stockLock.lock();
+                unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+            }
             //2.下单前处理
             try {
                 beforeOrderCreate(tradeOrderBO);
@@ -281,29 +332,43 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         } catch (Exception e) {
             log.error("下单失败:", e);
             return CommonResult.error(ORDER_CREATE_FAIL);
+        } finally {
+
         }
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeOrderRespDTO> createShipOrder(ShipTradeOrderCreateReqVO shipTradeOrderCreateReqVO) {
         TradeOrderBO tradeOrderBO = null;
+        RLock stockLock = null;
+        String stockLockKey = null;
+        boolean unlockRegistered = false;
         try {
             //1.构建订单
             tradeOrderBO = buildShipCreateOrder(shipTradeOrderCreateReqVO);
-            //2.下单前处理
-            beforeOrderCreate(tradeOrderBO);
-            //3.保存订单
-            tradeOrderRepositoryService.saveOrder(tradeOrderBO);
-            //4下单成功回调
-            tradeSupplierCallbackService.afterAllOrderCreate(tradeOrderBO.getTradeOrderDO());
-            //5.下单后处理
-            afterOrderCreate(tradeOrderBO);
-            TradeOrderRespDTO tradeOrderRespDTO = new TradeOrderRespDTO().setOrderId(tradeOrderBO.getTradeOrderDO().getId()).setOrderNo(tradeOrderBO.getTradeOrderDO().getOrderNo())
-                    .setPayAmount(tradeOrderBO.getTradeOrderDO().getPayAmount()).setOriginPayAmount(tradeOrderBO.getExtraInfo().getOriginPayAmount())
-                    .setDiscountAmount(tradeOrderBO.getExtraInfo().getDiscountAmount()).setMarketingId(tradeOrderBO.getExtraInfo().getMarketingCouponCodeId())
-                    .setOrderStatus(tradeOrderBO.getTradeOrderDO().getOrderStatus())
-                    .setMarketingName(tradeOrderBO.getExtraInfo().getMarketingName());
-            return CommonResult.success(tradeOrderRespDTO);
+            stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, tradeOrderBO.getTradeOrderDO().getVoyageId());
+            stockLock = redissonClient.getLock(stockLockKey);
+            try {
+                stockLock.lock();
+                unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                //2.下单前处理
+                beforeOrderCreate(tradeOrderBO);
+                //3.保存订单
+                tradeOrderRepositoryService.saveOrder(tradeOrderBO);
+                //4下单成功回调
+                tradeSupplierCallbackService.afterAllOrderCreate(tradeOrderBO.getTradeOrderDO());
+                //5.下单后处理
+                afterOrderCreate(tradeOrderBO);
+                TradeOrderRespDTO tradeOrderRespDTO = new TradeOrderRespDTO().setOrderId(tradeOrderBO.getTradeOrderDO().getId()).setOrderNo(tradeOrderBO.getTradeOrderDO().getOrderNo())
+                        .setPayAmount(tradeOrderBO.getTradeOrderDO().getPayAmount()).setOriginPayAmount(tradeOrderBO.getExtraInfo().getOriginPayAmount())
+                        .setDiscountAmount(tradeOrderBO.getExtraInfo().getDiscountAmount()).setMarketingId(tradeOrderBO.getExtraInfo().getMarketingCouponCodeId())
+                        .setOrderStatus(tradeOrderBO.getTradeOrderDO().getOrderStatus())
+                        .setMarketingName(tradeOrderBO.getExtraInfo().getMarketingName());
+                return CommonResult.success(tradeOrderRespDTO);
+            } finally {
+
+            }
         } catch (ServiceException e) {
             ExceptionUtils.log(log, "下单失败:", e);
             handleAfterOrderCreateFail(e.getCode(), tradeOrderBO);
@@ -315,34 +380,46 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeOrderRespDTO> modifyShipOrder(ShipTradeOrderCreateReqVO shipTradeOrderCreateReqVO) {
         TradeOrderBO tradeOrderBO = null;
+        RLock stockLock = null;
+        String stockLockKey = null;
+        boolean unlockRegistered = false;
         try {
             //1.构建订单
             tradeOrderBO = buildShipCreateOrder(shipTradeOrderCreateReqVO);
-            tradeOrderBO.setOldTradeOrderDO(shipTradeOrderCreateReqVO.getOldtradeOrderDO());
-            //2.下单前处理
-            beforeOrderCreate(tradeOrderBO);
-            //3.保存订单
-            tradeOrderBO.setUpdateTime(shipTradeOrderCreateReqVO.getUpdateTime());
+            stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, tradeOrderBO.getTradeOrderDO().getVoyageId());
+            stockLock = redissonClient.getLock(stockLockKey);
+            try {
+                stockLock.lock();
+                unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                tradeOrderBO.setOldTradeOrderDO(shipTradeOrderCreateReqVO.getOldtradeOrderDO());
+                //2.下单前处理
+                beforeOrderCreate(tradeOrderBO);
+                //3.保存订单
+                tradeOrderBO.setUpdateTime(shipTradeOrderCreateReqVO.getUpdateTime());
 
-            String changedFields = BeanUtils.getChangedFields(shipTradeOrderCreateReqVO.getOldtradeOrderDO(), tradeOrderBO.getTradeOrderDO());
-            System.err.println("changedFields=" + changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
-            if (tradeOrderBO.getTradeOrderBindDO() != null) {
-                tradeOrderBO.getTradeOrderBindDO().setRemark(changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
-            }
-            tradeOrderRepositoryService.modifyOrder(tradeOrderBO, shipTradeOrderCreateReqVO.getOldtradeOrderDO());
-            //4 下单成功回调
+                String changedFields = BeanUtils.getChangedFields(shipTradeOrderCreateReqVO.getOldtradeOrderDO(), tradeOrderBO.getTradeOrderDO());
+                System.err.println("changedFields=" + changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
+                if (tradeOrderBO.getTradeOrderBindDO() != null) {
+                    tradeOrderBO.getTradeOrderBindDO().setRemark(changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
+                }
+                tradeOrderRepositoryService.modifyOrder(tradeOrderBO, shipTradeOrderCreateReqVO.getOldtradeOrderDO());
+                //4 下单成功回调
 //            tradeSupplierCallbackService.afterAllOrderCreate(tradeOrderBO.getTradeOrderDO());
-            //5.下单后处理
-            afterOrderModify(tradeOrderBO, changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
-            TradeOrderRespDTO tradeOrderRespDTO = new TradeOrderRespDTO().setOrderId(tradeOrderBO.getTradeOrderDO().getId()).setOrderNo(tradeOrderBO.getTradeOrderDO().getOrderNo())
-                    .setPayAmount(tradeOrderBO.getTradeOrderDO().getPayAmount()).setOriginPayAmount(tradeOrderBO.getExtraInfo().getOriginPayAmount())
-                    .setDiscountAmount(tradeOrderBO.getExtraInfo().getDiscountAmount()).setMarketingId(tradeOrderBO.getExtraInfo().getMarketingCouponCodeId())
-                    .setMarketingName(tradeOrderBO.getExtraInfo().getMarketingName())
-                    .setOrderStatus(tradeOrderBO.getTradeOrderDO().getOrderStatus())
-                    .setContent(changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
-            return CommonResult.success(tradeOrderRespDTO);
+                //5.下单后处理
+                afterOrderModify(tradeOrderBO, changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
+                TradeOrderRespDTO tradeOrderRespDTO = new TradeOrderRespDTO().setOrderId(tradeOrderBO.getTradeOrderDO().getId()).setOrderNo(tradeOrderBO.getTradeOrderDO().getOrderNo())
+                        .setPayAmount(tradeOrderBO.getTradeOrderDO().getPayAmount()).setOriginPayAmount(tradeOrderBO.getExtraInfo().getOriginPayAmount())
+                        .setDiscountAmount(tradeOrderBO.getExtraInfo().getDiscountAmount()).setMarketingId(tradeOrderBO.getExtraInfo().getMarketingCouponCodeId())
+                        .setMarketingName(tradeOrderBO.getExtraInfo().getMarketingName())
+                        .setOrderStatus(tradeOrderBO.getTradeOrderDO().getOrderStatus())
+                        .setContent(changedFields + shipTradeOrderCreateReqVO.getChangedFields1());
+                return CommonResult.success(tradeOrderRespDTO);
+            } finally {
+
+            }
         } catch (ServiceException e) {
             try {
                 ExceptionUtils.log(log, "订单修改失败:", e);

+ 84 - 19
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java

@@ -152,7 +152,10 @@ import org.jetbrains.annotations.NotNull;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.io.*;
@@ -203,6 +206,37 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Resource
     private NotifyMessageSendApi notifyMessageSendApi;
 
+    private boolean registerUnlockAfterTransaction(RLock lock, String lockKey) {
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            log.warn("当前无事务同步,航次锁将在 finally 中释放,lockKey:{}", lockKey);
+            return false;
+        }
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCompletion(int status) {
+                log.info("订单事务完成,释放航次锁,lockKey:{},status:{}", lockKey, status);
+                unlockStockLock(lock, lockKey);
+            }
+        });
+        return true;
+    }
+
+    private void unlockStockLock(RLock lock, String lockKey) {
+        if (lock == null) {
+            return;
+        }
+        try {
+            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.info("航次锁释放完成,lockKey:{}", lockKey);
+            } else {
+                log.warn("跳过航次锁释放,当前线程未持有锁,lockKey:{}", lockKey);
+            }
+        } catch (Exception e) {
+            log.error("航次锁释放异常,lockKey:{}", lockKey, e);
+        }
+    }
+
     @Resource
     private ResourceRoomModelService resourceRoomModelService;
     @Resource
@@ -445,7 +479,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
     @Override
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_AUDIT)
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TenantIgnore
     public CommonResult<?> orderAudit(Long orderId, Integer auditStatus,String auditAdvice) {
         TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(orderId);
@@ -627,12 +661,20 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 List<OrderRoomUseDTO> orderRoomUseDTOList = tradeVisitorMapper.selectRoomShipVisitor2(lastTradeOrder.getId());
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
-                Integer orderStatus = reqDTO.getOrderStatus();
-                if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
-                    voyageApi.preReduceStock(reqDTO);
-                } else {
-                    reqDTO.setOldOrderStatus(lastTradeOrder.getOrderStatus());
-                    voyageApi.reduceStock(reqDTO);
+                String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, reqDTO.getVoyageId());
+                RLock stockLock = redissonClient.getLock(stockLockKey);
+                boolean unlockRegistered = false;
+                try {
+                    stockLock.lock();
+                    unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                    Integer orderStatus = reqDTO.getOrderStatus();
+                    if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
+                        voyageApi.preReduceStock(reqDTO);
+                    } else {
+                        reqDTO.setOldOrderStatus(lastTradeOrder.getOrderStatus());
+                        voyageApi.reduceStock(reqDTO);
+                    }
+                } finally {
                 }
 
                 Map<String, Object> extMap = new HashMap<>();
@@ -1432,7 +1474,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
     @Override
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.CANCEL_AUDIT)
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TenantIgnore
     public CommonResult<?> cancelAuto(Long orderId) {
         TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(orderId);
@@ -1492,12 +1534,21 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
             List<OrderRoomUseDTO> orderRoomUseDTOList = tradeVisitorMapper.selectRoomShipVisitor2(lastTradeOrder.getId());
             reqDTO.setOrderRoomList(orderRoomUseDTOList);
-            Integer orderStatus = reqDTO.getOrderStatus();
-            if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
-                voyageApi.preReduceStock(reqDTO);
-            } else {
-                reqDTO.setOldOrderStatus(lastTradeOrder.getOrderStatus());
-                voyageApi.reduceStock(reqDTO);
+            String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, reqDTO.getVoyageId());
+            RLock stockLock = redissonClient.getLock(stockLockKey);
+            boolean unlockRegistered = false;
+            try {
+                stockLock.lock();
+                unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                Integer orderStatus = reqDTO.getOrderStatus();
+                if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
+                    voyageApi.preReduceStock(reqDTO);
+                } else {
+                    reqDTO.setOldOrderStatus(lastTradeOrder.getOrderStatus());
+                    voyageApi.reduceStock(reqDTO);
+                }
+            } finally {
+
             }
 
 
@@ -2327,11 +2378,12 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_CREATE)
     public CommonResult<?> createOtcOrder(LoginUser loginUser, TradeOrderOtcCreateVO createVO) {
         Long distributorId = createVO.getDistributorId() != null ? createVO.getDistributorId() : loginUser.getDistributorId();
         Long storeId = createVO.getStoreId() != null ? createVO.getStoreId() : loginUser.getStoreId();
+        Long voyageId = createVO.getVoyageId();
         //获取当前分销商信息
         if (distributorId == null) {
             return CommonResult.error(ORDER_OTC_FAIL);
@@ -2346,7 +2398,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         if (storeId != null) {
             storeInfo = storeApi.getStoreInfo(storeId);
         }
-        VoyageRespDTO voyage = productApi.getVoyage(createVO.getVoyageId());
+        VoyageRespDTO voyage = productApi.getVoyage(voyageId);
         RouteRespDTO routeRespDTO = routeApi.queryRouteById(voyage.getRouteId());
         ShipRespDTO ship = shipApi.queryShip(createVO.getShipId());
 
@@ -2363,13 +2415,19 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
             ccode = ship.getCode() + "-" + date + "-" + code + "-";
         }
 
-        int maxOrderNo = tradeOrderService.getMaxOrderNo(createVO.getVoyageId(),ccode);
+        int maxOrderNo = tradeOrderService.getMaxOrderNo(voyageId,ccode);
         String orderNo = ccode+(maxOrderNo + 1);
         //现在给整个订单号的加锁
         String lockKey = String.format(ProductRedisKeyConstants.ORDER_REDIS_KEY_PREFIX, orderNo);
         RLock lock = redissonClient.getLock(lockKey);
+        //现在给整个航次的库存加锁
+        String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
+        RLock stockLock = redissonClient.getLock(stockLockKey);
+        boolean stockUnlockRegistered = false;
         try {
             lock.lock(60, TimeUnit.SECONDS);
+            stockLock.lock();
+            stockUnlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
             TradeOrderTotalVO summary = createVO.getSummary();
             BigDecimal realAmount = summary.getRealMoney();
             BigDecimal orderMoney = summary.getOrderMoney();
@@ -2662,13 +2720,14 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_MODIFY)
     public CommonResult<?> motifyOtcOrder(LoginUser loginUser, TradeOrderOtcCreateVO createVO) {
         // 获取分销商id
         Long distributorId = createVO.getDistributorId() != null ? createVO.getDistributorId() : loginUser.getDistributorId();
         // 获取门店id
         Long storeId = createVO.getStoreId() != null ? createVO.getStoreId() : loginUser.getStoreId();
+        Long voyageId = createVO.getVoyageId();
         //获取当前分销商信息
         if (distributorId == null) {
             return CommonResult.error(ORDER_OTC_FAIL);
@@ -2700,7 +2759,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         int auditType = 0;
         String orderNo = tradeOrderDO.getOrderNo();
         Long oldvoyageId = tradeOrderDO.getVoyageId();
-        Long newvoyageId = createVO.getVoyageId();
+        Long newvoyageId = voyageId;
         List<TradeOrderPolicyReqVO> policyList = createVO.getPolicyList();
         // 航次ID
         if (newvoyageId == null) {
@@ -2716,8 +2775,14 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         //现在给整个订单号的加锁
         String lockKey = String.format(ProductRedisKeyConstants.ORDER_REDIS_KEY_PREFIX, orderNo);
         RLock lock = redissonClient.getLock(lockKey);
+        //现在给整个航次的库存加锁
+        String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
+        RLock stockLock = redissonClient.getLock(stockLockKey);
+        boolean stockUnlockRegistered = false;
         try {
             lock.lock(60, TimeUnit.SECONDS);
+            stockLock.lock();
+            stockUnlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
             Date updateTime = new Date();
             // 构建创建订单的参数
             ShipTradeOrderCreateReqVO shipTradeOrderCreateReqVO = BeanUtils.toBean(createVO, ShipTradeOrderCreateReqVO.class);

+ 49 - 5
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/pay/impl/TradeOrderPayServiceImpl.java

@@ -29,6 +29,7 @@ import com.yc.ship.module.product.api.VoyageApi;
 import com.yc.ship.module.product.api.dto.OrderRoomUseDTO;
 import com.yc.ship.module.product.api.dto.ReduceStockReqDTO;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.trade.controller.admin.order.vo.order.PayOrderReqVO;
 import com.yc.ship.module.trade.controller.admin.order.vo.order.PayOrderRespVO;
 import com.yc.ship.module.trade.dal.dataobject.order.*;
@@ -57,7 +58,10 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -128,6 +132,37 @@ public class TradeOrderPayServiceImpl implements TradeOrderPayService {
     @Resource
     private TradeOrderLogMapper tradeOrderLogMapper;
 
+    private boolean registerUnlockAfterTransaction(RLock lock, String lockKey) {
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            log.warn("当前无事务同步,航次锁将在 finally 中释放,lockKey:{}", lockKey);
+            return false;
+        }
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCompletion(int status) {
+                log.info("订单事务完成,释放航次锁,lockKey:{},status:{}", lockKey, status);
+                unlockStockLock(lock, lockKey);
+            }
+        });
+        return true;
+    }
+
+    private void unlockStockLock(RLock lock, String lockKey) {
+        if (lock == null) {
+            return;
+        }
+        try {
+            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.info("航次锁释放完成,lockKey:{}", lockKey);
+            } else {
+                log.warn("跳过航次锁释放,当前线程未持有锁,lockKey:{}", lockKey);
+            }
+        } catch (Exception e) {
+            log.error("航次锁释放异常,lockKey:{}", lockKey, e);
+        }
+    }
+
     @Override
     public CommonResult<PayOrderRespVO> payOrder(PayOrderReqVO payOrderReqVO) {
         try {
@@ -497,7 +532,7 @@ public class TradeOrderPayServiceImpl implements TradeOrderPayService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MANUL_CANCEL)
     public CommonResult<?> orderPayCancelHandler(Long orderId) {
         String lockKey = String.format(UPDATE_ORDER_LOCK, orderId);
@@ -538,10 +573,19 @@ public class TradeOrderPayServiceImpl implements TradeOrderPayService {
                     }
                 });
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
-                if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
-                    voyageApi.preCancelReduceStock(reqDTO);
-                } else {
-                    voyageApi.cancelReduceStock(reqDTO);
+                String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, reqDTO.getVoyageId());
+                RLock stockLock = redissonClient.getLock(stockLockKey);
+                boolean unlockRegistered = false;
+                try {
+                    stockLock.lock();
+                    unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                    if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
+                        voyageApi.preCancelReduceStock(reqDTO);
+                    } else {
+                        voyageApi.cancelReduceStock(reqDTO);
+                    }
+                } finally {
+
                 }
 
                 //添加订单日志

+ 59 - 3
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/refund/impl/TradeRefundServiceImpl.java

@@ -28,6 +28,7 @@ import com.yc.ship.module.product.api.dto.ProductSpuRespDTO;
 import com.yc.ship.module.product.api.dto.ReduceStockReqDTO;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
 import com.yc.ship.module.product.enums.RefundRuleEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.trade.api.dto.*;
 import com.yc.ship.module.trade.controller.admin.order.vo.refund.TradeRefundAmountReqVO;
 import com.yc.ship.module.trade.convert.order.TradeRefundConvert;
@@ -67,6 +68,10 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -137,6 +142,37 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     @Resource
     private RefundMapper refundMapper;
 
+    private boolean registerUnlockAfterTransaction(RLock lock, String lockKey) {
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            log.warn("当前无事务同步,航次锁将在 finally 中释放,lockKey:{}", lockKey);
+            return false;
+        }
+        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+            @Override
+            public void afterCompletion(int status) {
+                log.info("订单事务完成,释放航次锁,lockKey:{},status:{}", lockKey, status);
+                unlockStockLock(lock, lockKey);
+            }
+        });
+        return true;
+    }
+
+    private void unlockStockLock(RLock lock, String lockKey) {
+        if (lock == null) {
+            return;
+        }
+        try {
+            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.info("航次锁释放完成,lockKey:{}", lockKey);
+            } else {
+                log.warn("跳过航次锁释放,当前线程未持有锁,lockKey:{}", lockKey);
+            }
+        } catch (Exception e) {
+            log.error("航次锁释放异常,lockKey:{}", lockKey, e);
+        }
+    }
+
 
     @Override
     public CommonResult<TradeRefundRespDTO> refund(TradeRefundCreateReqDTO refundCreateReqDTO) {
@@ -282,6 +318,7 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeRefundAuditRespDTO> refundAudit(TradeRefundAuditReqDTO refundAuditReqDTO) {
         //创建审核记录
         RefundDO refundDO = tradeRefundRepositoryService.getById(refundAuditReqDTO.getRefundId());
@@ -317,10 +354,19 @@ public class TradeRefundServiceImpl implements TradeRefundService {
                 reqDTO.setUseRoomNum(BigDecimal.valueOf(tradeOrderTotal.getUseRoomTotalNum()));
                 List<OrderRoomUseDTO> orderRoomUseDTOList = tradeVisitorMapper.selectRefundRoomShipVisitor(tradeOrderDO.getId());
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
-                voyageApi.cancelReduceStockNew(reqDTO);
+                String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, reqDTO.getVoyageId());
+                RLock stockLock = redissonClient.getLock(stockLockKey);
+                boolean unlockRegistered = false;
+                try {
+                    stockLock.lock();
+                    unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                    voyageApi.cancelReduceStockNew(reqDTO);
+                } finally {
+
+                }
             } catch (Exception e) {
-                e.printStackTrace();
                 log.error("取消库存cancelReduceStock失败", e);
+                throw new RuntimeException("取消库存失败:" + e.getMessage(), e);
             }
             //发送审核通知消息
             tradePublishUtils.publishRefundAuditMsg(refundAuditDO.getId().toString());
@@ -488,6 +534,7 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<?> refundByPaymentType(Long refundId) {
         String lockKey = String.format(UPDATE_REFUND_LOCK, refundId);
         RLock lock = redissonClient.getLock(lockKey);
@@ -552,7 +599,16 @@ public class TradeRefundServiceImpl implements TradeRefundService {
                     }
                 });
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
-                voyageApi.cancelReduceStock(reqDTO);
+                String stockLockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, reqDTO.getVoyageId());
+                RLock stockLock = redissonClient.getLock(stockLockKey);
+                boolean unlockRegistered = false;
+                try {
+                    stockLock.lock();
+                    unlockRegistered = registerUnlockAfterTransaction(stockLock, stockLockKey);
+                    voyageApi.cancelReduceStock(reqDTO);
+                } finally {
+
+                }
             }
         } catch (ServiceException e) {
             e.printStackTrace();