From d09efe6812f01d770e7f125f365459da25704d0d Mon Sep 17 00:00:00 2001
From: yiwenpeng <ywp303@163.com>
Date: Mon, 3 Jun 2024 23:08:27 +0800
Subject: [PATCH] feat:并发问题:如果多个线程同时修改同一个shipmentDetail的状态,可能会导致数据不一致,用redis锁解决

---
 src/main/java/com/huaheng/framework/config/ShiroConfig.java                   |  10 +++++-----
 src/main/java/com/huaheng/pc/task/taskHeader/service/ReceiptTaskService.java  |   3 +++
 src/main/java/com/huaheng/pc/task/taskHeader/service/ShipmentTaskService.java | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------
 3 files changed, 114 insertions(+), 42 deletions(-)

diff --git a/src/main/java/com/huaheng/framework/config/ShiroConfig.java b/src/main/java/com/huaheng/framework/config/ShiroConfig.java
index 46e64c8..d1eda63 100644
--- a/src/main/java/com/huaheng/framework/config/ShiroConfig.java
+++ b/src/main/java/com/huaheng/framework/config/ShiroConfig.java
@@ -131,7 +131,7 @@ public class ShiroConfig {
     public SpringSessionValidationScheduler sessionValidationScheduler() {
         SpringSessionValidationScheduler sessionValidationScheduler = new SpringSessionValidationScheduler();
         // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
-        sessionValidationScheduler.setSessionValidationInterval(validationInterval * 60 * 1000);
+        sessionValidationScheduler.setSessionValidationInterval(validationInterval * 60 * 4000);
         // 设置会话验证调度器进行会话验证时的会话管理器
         sessionValidationScheduler.setSessionManager(sessionValidationManager());
         return sessionValidationScheduler;
@@ -148,7 +148,7 @@ public class ShiroConfig {
         // 删除过期的session
         manager.setDeleteInvalidSessions(true);
         // 设置全局session超时时间
-        manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
+        manager.setGlobalSessionTimeout(expireTime * 60 * 4000);
         // 去掉 JSESSIONID
         manager.setSessionIdUrlRewritingEnabled(false);
         // 是否定时检查session
@@ -171,7 +171,7 @@ public class ShiroConfig {
         // 删除过期的session
         manager.setDeleteInvalidSessions(true);
         // 设置全局session超时时间
-        manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
+        manager.setGlobalSessionTimeout(expireTime * 60 * 4000);
         // 去掉 JSESSIONID
         manager.setSessionIdUrlRewritingEnabled(false);
         // 定义要使用的无效的Session定时调度器
@@ -288,7 +288,7 @@ public class ShiroConfig {
         //filterChainDefinitionMap.put("/mobile/inventory/completeTaskListByWMS", "anon");
         //filterChainDefinitionMap.put("/receipt/receiving/saveBatch", "anon");
         //filterChainDefinitionMap.put("/config/zone/getAllFlatLocation", "anon");
-        //filterChainDefinitionMap.put("/mobile/", "anon");
+        //filterChainDefinitionMap.put("/mobile/getModules2", "anon");
 
 
         // 系统权限列表
@@ -337,7 +337,7 @@ public class ShiroConfig {
         cookie.setDomain(domain);
         cookie.setPath(path);
         cookie.setHttpOnly(httpOnly);
-        cookie.setMaxAge(maxAge * 24 * 60 * 60);
+        cookie.setMaxAge(maxAge * 24 * 60 * 240);
         return cookie;
     }
 
diff --git a/src/main/java/com/huaheng/pc/task/taskHeader/service/ReceiptTaskService.java b/src/main/java/com/huaheng/pc/task/taskHeader/service/ReceiptTaskService.java
index e2fe2fe..9717d98 100644
--- a/src/main/java/com/huaheng/pc/task/taskHeader/service/ReceiptTaskService.java
+++ b/src/main/java/com/huaheng/pc/task/taskHeader/service/ReceiptTaskService.java
@@ -531,6 +531,9 @@ public class ReceiptTaskService {
                 .eq(InventoryDetail::getReceiptDetailId, taskDetail.getBillDetailId());
         InventoryDetail inventoryDetail = inventoryDetailService.getOne(inventory);
         Material material = materialService.getMaterialByCode(receiptDetail.getMaterialCode(), warehouseCode);
+        if (material == null) {
+            throw new ServiceException("物料不存在," + receiptDetail.getMaterialCode());
+        }
         /*单位换算*/
         BigDecimal receiptQty = taskDetail.getQty();
         if (StringUtils.isNotEmpty(receiptDetail.getMaterialUnit()) && StringUtils.isNotEmpty(material.getUnit()) && !receiptDetail.getMaterialUnit().equals(material.getUnit())) {
diff --git a/src/main/java/com/huaheng/pc/task/taskHeader/service/ShipmentTaskService.java b/src/main/java/com/huaheng/pc/task/taskHeader/service/ShipmentTaskService.java
index 1356529..4c63755 100644
--- a/src/main/java/com/huaheng/pc/task/taskHeader/service/ShipmentTaskService.java
+++ b/src/main/java/com/huaheng/pc/task/taskHeader/service/ShipmentTaskService.java
@@ -46,6 +46,9 @@ import com.huaheng.pc.task.taskHeader.domain.ShipmentTaskCreateModel;
 import com.huaheng.pc.task.taskHeader.domain.TaskHeader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -53,6 +56,9 @@ import javax.annotation.Resource;
 import javax.validation.constraints.NotNull;
 import java.math.BigDecimal;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 入库任务创建和完成
@@ -102,6 +108,9 @@ public class ShipmentTaskService {
     @Resource
     private StationService stationService;
 
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
     /**
      * 创建出库任务
      *
@@ -498,46 +507,110 @@ public class ShipmentTaskService {
 
         //修改出库单状态
         List<TaskDetail> taskDetailList = taskDetailService.findByTaskId(task.getId());
-        HashSet<Integer> ids = new HashSet<>();
+        // 使用 Set 存储 billDetailId 和 shipmentId,避免重复查询
+        Set<Integer> billDetailIds = new HashSet<>();
+        Set<Integer> shipmentIds = new HashSet<>();
         for (TaskDetail taskDetail : taskDetailList) {
-            ShipmentDetail shipmentDetail = shipmentDetailService.getById(taskDetail.getBillDetailId());
-            if (StringUtils.isNotNull(shipmentDetail)) {
+            billDetailIds.add(taskDetail.getBillDetailId());
+        }
+
+        // 批量查询出库单明细
+        Map<Integer, ShipmentDetail> shipmentDetailMap = shipmentDetailService.listByIds(billDetailIds)
+                .stream().collect(Collectors.toMap(ShipmentDetail::getId, Function.identity()));
+
+        // 添加 shipmentId 到 shipmentIds 中
+        for (TaskDetail taskDetail : taskDetailList) {
+            ShipmentDetail shipmentDetail = shipmentDetailMap.get(taskDetail.getBillDetailId());
+            if (shipmentDetail != null) {
+                shipmentIds.add(shipmentDetail.getShipmentId());
+            }
+        }
+
+        // 批量查询出库单头信息
+        Map<Integer, ShipmentHeader> shipmentHeaderMap = shipmentHeaderService.listByIds(shipmentIds)
+                .stream().collect(Collectors.toMap(ShipmentHeader::getId, Function.identity()));
+
+        for (TaskDetail taskDetail : taskDetailList) {
+            ShipmentDetail shipmentDetail = shipmentDetailMap.get(taskDetail.getBillDetailId());
+            if (shipmentDetail != null) {
                 if (shipmentDetail.getQty().compareTo(shipmentDetail.getTaskQty()) == 0) {
-                    //一条单据明细可能有多条组盘多条任务
-                    List<ShipmentContainerDetail> list = shipmentContainerDetailService.list(new LambdaQueryWrapper<ShipmentContainerDetail>()
-                            .eq(ShipmentContainerDetail::getShipmentDetailId, shipmentDetail.getId()));
-                    boolean flag = true;
-                    for (ShipmentContainerDetail shipmentContainerDetail : list) {
-                        if (shipmentContainerDetail.getStatus() != 20) {
-                            flag = false;
-                            break;
-                        }
-                    }
-                    if (flag) {
+                    // 一条单据明细,可能有多条组盘多条任务
+                    List<ShipmentContainerDetail> list = shipmentContainerDetailService.list(
+                            new LambdaQueryWrapper<ShipmentContainerDetail>().eq(ShipmentContainerDetail::getShipmentDetailId, shipmentDetail.getId()));
+                    boolean allCompleted = list.stream().allMatch(detail -> detail.getStatus() == 20);
+
+                    if (allCompleted) {
                         shipmentDetail.setStatus(QuantityConstant.SHIPMENT_HEADER_COMPLETED);
                     } else {
                         shipmentDetail.setStatus(QuantityConstant.SHIPMENT_HEADER_GROUPDISK);
                     }
-                    shipmentDetailService.updateById(shipmentDetail);
+
+                    // 使用 Redis 处理并发更新 shipmentDetail
+                    updateShipmentDetailWithRedis(shipmentDetail);
+                }
+                // 更新出库单头信息
+                updateShipmentHeaderStatus(shipmentDetail.getShipmentId(), shipmentHeaderMap);
+            }
+        }
+        //删除自建单据物料
+        for (String materialCode : materialCodes) {
+            List<InventoryDetail> list = inventoryDetailService.list(new LambdaUpdateWrapper<InventoryDetail>().eq(InventoryDetail::getMaterialCode, materialCode));
+            if (list.isEmpty()) {
+                Material material = materialService.getMaterialByCode(materialCode, "CS0001");
+                if (material != null && material.getSelfCreated()) {
+                    materialService.removeById(material);
                 }
-                ids.add(shipmentDetail.getShipmentId());
             }
 
         }
 
-        /* 更新出库单状态 */
-        for (Integer id : ids) {
-            ShipmentHeader shipmentHeader = shipmentHeaderService.getById(id);
-            if (shipmentHeader != null) {
+        return AjaxResult.success("完成出库任务成功");
+    }
+
+    // 使用 Redis 处理并发更新 shipmentDetail
+    private void updateShipmentDetailWithRedis(ShipmentDetail shipmentDetail) {
+        String lockKey = "shipmentDetailLock:" + shipmentDetail.getId(); // Redis 锁的 key
+        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
+        Boolean lockAcquired = valueOperations.setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
+
+        if (lockAcquired != null && lockAcquired) {
+            try {
+                // 获取锁成功,进行更新操作
+                shipmentDetailService.updateById(shipmentDetail); // 直接更新数据库
+            } finally {
+                redisTemplate.delete(lockKey); // 释放锁
+            }
+        } else {
+            // 获取锁失败,可以考虑重试或其他处理
+            throw new ServiceException("更新出库单明细失败,请稍后再试");
+        }
+    }
+
+    // 更新出库单头信息
+    private void updateShipmentHeaderStatus(Integer shipmentId, Map<Integer, ShipmentHeader> shipmentHeaderMap) {
+        // 构造 Redis 锁的 key
+        String lockKey = "shipmentHeaderLock:" + shipmentId;
+        // 获取 Redis 操作对象
+        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
+
+        // 尝试获取锁
+        Boolean lockAcquired = valueOperations.setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
+
+        if (lockAcquired != null && lockAcquired) {
+            try {
+                ShipmentHeader shipmentHeader = shipmentHeaderMap.get(shipmentId);
+                if (shipmentHeader == null) {
+                    return;
+                }
+
+                // 获取状态信息并更新出库单头逻辑
                 Map<String, Integer> status = shipmentDetailService.selectStatus(shipmentHeader.getId());
                 Integer maxStatus = status.get("maxStatus");
                 Integer minStatus = status.get("minStatus");
 
                 // 检查 maxStatus 和 minStatus 是否不为空
                 if (maxStatus != null && minStatus != null) {
-                    boolean isStatusCompleted = QuantityConstant.SHIPMENT_HEADER_COMPLETED.equals(maxStatus);
-
-                    if (isStatusCompleted) {
+                    if (QuantityConstant.SHIPMENT_HEADER_COMPLETED.equals(maxStatus)) {
                         shipmentHeader.setFirstStatus(QuantityConstant.SHIPMENT_HEADER_COMPLETED);
                     }
 
@@ -548,26 +621,22 @@ public class ShipmentTaskService {
                     }
                 }
                 shipmentHeader.setLastUpdated(new Date());
+
+                // 使用乐观锁更新出库单头信息
                 if (!shipmentHeaderService.updateById(shipmentHeader)) {
-                    throw new ServiceException("更新入库单头表失败");
+                    throw new ServiceException("更新出库单头表失败");
                 }
+            } finally {
+                // 释放锁
+                redisTemplate.delete(lockKey);
             }
+        } else {
+            // 获取锁失败,可以考虑重试或者其他处理方式
+            throw new ServiceException("获取锁失败,请稍后再试");
         }
-        //删除自建单据物料
-        for (String materialCode : materialCodes) {
-            List<InventoryDetail> list = inventoryDetailService.list(new LambdaUpdateWrapper<InventoryDetail>().eq(InventoryDetail::getMaterialCode, materialCode));
-            if (list.isEmpty()) {
-                Material material = materialService.getMaterialByCode(materialCode, "CS0001");
-                if (material != null && material.getSelfCreated()) {
-                    materialService.removeById(material);
-                }
-            }
-
-        }
-
-        return AjaxResult.success("完成出库任务成功");
     }
 
+
     /**
      * 出库任务完成时更新库位和容器状态
      *
--
libgit2 0.22.2