haogh 3 周之前
父節點
當前提交
5b3aed8cd9

+ 5 - 0
src/main/java/com/qmth/exam/reserve/cache/CacheConstants.java

@@ -102,4 +102,9 @@ public interface CacheConstants {
      */
     String LOCK_ARRANGE_EXAM = "arrange_exam:%s";
 
+    /**
+     * 考点容量变更操作锁
+     * $lock:lock_exam_site_change_capacity:{examSiteId}
+     */
+    String LOCK_EXAM_SITE_CHANGE_CAPACITY = "exam_site_change_capacity:%s";
 }

+ 154 - 80
src/main/java/com/qmth/exam/reserve/service/impl/ExamRoomServiceImpl.java

@@ -31,6 +31,7 @@ import com.qmth.exam.reserve.util.DateUtil;
 import com.qmth.exam.reserve.util.PageUtil;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RLock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -96,44 +97,68 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
         String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, req.getExamSiteId());
         if (concurrentService.isLocked(lockKey)) {
             log.warn("[考场保存]考点剩余可约数量更新中,不允许操作修改!lockKey:{}", lockKey);
-            throw new StatusException("系统正在更新可预约数量,不允许修改考场");
+            throw new StatusException("系统正在更新可预约数量,请稍后重试");
         }
 
-        checkExamRoom(req);
+        //考点容量变更锁
+        String examSiteLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CHANGE_CAPACITY, req.getExamSiteId());
+        RLock examSiteLock = (RLock) concurrentService.getLock(examSiteLockKey);
 
-        ExamRoomEntity examRoomEntity = new ExamRoomEntity();
-        BeanUtils.copyProperties(req, examRoomEntity);
+        try {
+            if (!examSiteLock.tryLock()) {
+                log.warn("[考场保存] 获取锁失败,同一个教学点不允许同时操作一个考点的容量修改, lockKey:{}", examSiteLockKey);
+                throw new StatusException("其他老师正在修改该考点的容量,不允许同时操作,请稍后再试!");
+            } else {
+                //校验
+                checkExamRoom(req);
+
+                ExamRoomEntity examRoomEntity = new ExamRoomEntity();
+                BeanUtils.copyProperties(req, examRoomEntity);
+
+                ExamRoomEntity beforeUpdateRoom = new ExamRoomEntity();
+                //考点容量
+                ExamSiteEntity examSite = examSiteService.getById(req.getExamSiteId());
+                int oldCapacity = examSite.getCapacity();
+                int newCapacity;
+                if (req.getId() == null) {
+                    examRoomEntity.setEnable(Boolean.TRUE);
+                    save(examRoomEntity);
+                } else {
+                    beforeUpdateRoom = getById(req.getId());
+
+                    if (req.getCapacity() != null && beforeUpdateRoom.getCapacity() != null && beforeUpdateRoom.getCapacity() > req.getCapacity()) {
+                        checkCapacity(beforeUpdateRoom, beforeUpdateRoom.getCapacity() - req.getCapacity());
+                    }
+
+                    updateById(examRoomEntity);
+                }
+                //更新考点容量和教学点容量
+                updateExamSiteAndTeachingCapacity(req.getExamSiteId());
+                //考场更换考点
+                if (req.getId() != null && !req.getExamSiteId().equals(beforeUpdateRoom.getExamSiteId())) {
+                    updateExamSiteAndTeachingCapacity(beforeUpdateRoom.getExamSiteId());
+                }
 
-        ExamRoomEntity beforeUpdateRoom = new ExamRoomEntity();
-        //考点容量
-        ExamSiteEntity examSite = examSiteService.getById(req.getExamSiteId());
-        int oldCapacity = examSite.getCapacity();
-        int newCapacity;
-        if (req.getId() == null) {
-            examRoomEntity.setEnable(Boolean.TRUE);
-            save(examRoomEntity);
-        } else {
-            beforeUpdateRoom = getById(req.getId());
+                // 获取更新后的容量
+                examSite = examSiteService.getById(req.getExamSiteId());
+                newCapacity = examSite.getCapacity();
 
-            if (req.getCapacity() != null && beforeUpdateRoom.getCapacity() != null && beforeUpdateRoom.getCapacity() > req.getCapacity()) {
-                checkCapacity(beforeUpdateRoom, beforeUpdateRoom.getCapacity() - req.getCapacity());
+                //刷新缓存
+                doRefreshApplyCountCache(examRoomEntity.getId(), examRoomEntity.getExamSiteId(), oldCapacity, newCapacity);
+            }
+        } catch (Exception e) {
+            log.error("[考场保存]保存失败,原因:{}", e.getMessage(), e);
+            throw new StatusException(e.getMessage());
+        } finally {
+            try {
+                if (examSiteLock.isLocked() && examSiteLock.isHeldByCurrentThread()) {
+                    examSiteLock.unlock();
+                    log.info("[考场保存] 解锁成功,lockKey:{}", examSiteLockKey);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
             }
-
-            updateById(examRoomEntity);
-        }
-        //更新考点容量和教学点容量
-        updateExamSiteAndTeachingCapacity(req.getExamSiteId());
-        //考场更换考点
-        if (req.getId() != null && !req.getExamSiteId().equals(beforeUpdateRoom.getExamSiteId())) {
-            updateExamSiteAndTeachingCapacity(beforeUpdateRoom.getExamSiteId());
         }
-
-        // 获取更新后的容量
-        examSite = examSiteService.getById(req.getExamSiteId());
-        newCapacity = examSite.getCapacity();
-
-        //刷新缓存
-        doRefreshApplyCountCache(examRoomEntity.getId(), examRoomEntity.getExamSiteId(), oldCapacity, newCapacity);
     }
 
     private void checkCapacity(ExamRoomEntity beforeUpdateRoom, Integer decreaseCapacity) {
@@ -264,27 +289,52 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
             throw new StatusException("系统正在更新可预约数量,不允许启用/禁用考场");
         }
 
-        //考点容量
-        ExamSiteEntity examSite = examSiteService.getById(examRoom.getExamSiteId());
-        int oldCapacity = examSite.getCapacity();
+        //考点容量变更锁
+        String examSiteLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CHANGE_CAPACITY, examRoom.getExamSiteId());
+        RLock examSiteLock = (RLock) concurrentService.getLock(examSiteLockKey);
 
-        //禁用的时候,判断容量
-        if (!enable) {
-            checkCapacity(examRoom, examRoom.getCapacity());
-        }
+        try {
+            if (!examSiteLock.tryLock()) {
+                log.warn("[考场禁用/启用] 获取锁失败,同一个教学点不允许同时操作一个考点的容量修改, lockKey:{}", examSiteLockKey);
+                throw new StatusException("其他老师正在修改该考点的容量,不允许同时操作,请稍后再试!");
+            } else {
+                //考点容量
+                ExamSiteEntity examSite = examSiteService.getById(examRoom.getExamSiteId());
+                int oldCapacity = examSite.getCapacity();
+
+                //禁用的时候,判断容量
+                if (!enable) {
+                    checkCapacity(examRoom, examRoom.getCapacity());
+                }
 
-        LambdaUpdateWrapper<ExamRoomEntity> wrapper = new LambdaUpdateWrapper<>();
-        wrapper.set(ExamRoomEntity::getEnable, enable);
-        wrapper.eq(ExamRoomEntity::getId, id);
-        update(null, wrapper);
+                LambdaUpdateWrapper<ExamRoomEntity> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.set(ExamRoomEntity::getEnable, enable);
+                wrapper.eq(ExamRoomEntity::getId, id);
+                update(null, wrapper);
 
-        updateExamSiteAndTeachingCapacity(examRoom.getExamSiteId());
+                updateExamSiteAndTeachingCapacity(examRoom.getExamSiteId());
+
+                //更新之后考点的容量
+                examSite = examSiteService.getById(examRoom.getExamSiteId());
+
+                //刷新缓存
+                doRefreshApplyCountCache(id, examRoom.getExamSiteId(), oldCapacity, examSite.getCapacity());
+            }
+        } catch (Exception e) {
+            log.error("[考场禁用/启用]保存失败,原因:{}", e.getMessage(), e);
+            throw new StatusException(e.getMessage());
+        } finally {
+            try {
+                if (examSiteLock.isLocked() && examSiteLock.isHeldByCurrentThread()) {
+                    examSiteLock.unlock();
+                    log.info("[考场禁用/启用] 解锁成功,lockKey:{}", examSiteLockKey);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
+            }
+        }
 
-        //更新之后考点的容量
-        examSite = examSiteService.getById(examRoom.getExamSiteId());
 
-        //刷新缓存
-        doRefreshApplyCountCache(id, examRoom.getExamSiteId(), oldCapacity, examSite.getCapacity());
     }
 
     @Transactional
@@ -412,45 +462,69 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
     private void saveRoom(ExamRoomEntity room) {
         String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, room.getExamSiteId());
         if (concurrentService.isLocked(lockKey)) {
-            log.warn("[考场保存]考点剩余可约数量更新中,不允许导入考场!lockKey:{}", lockKey);
+            log.warn("[考场导入]考点剩余可约数量更新中,不允许导入考场!lockKey:{}", lockKey);
             throw new StatusException("系统正在更新可预约数量,不允许导入考场");
         }
 
-        //保存之前的考点容量
-        ExamSiteEntity examSite = examSiteService.getById(room.getExamSiteId());
-        if (examSite == null) {
-            throw new StatusException("找不到考点");
-        }
-        int oldCapacity = examSite.getCapacity();
-        Long roomId;
+        //考点容量变更锁
+        String examSiteLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CHANGE_CAPACITY, room.getExamSiteId());
+        RLock examSiteLock = (RLock) concurrentService.getLock(examSiteLockKey);
 
-        ExamRoomEntity examRoom = getExamRoom(room.getExamSiteId(), room.getCode());
-        if (examRoom != null) {
-
-            // 校验容量
-            if (examRoom.getCapacity() != null && room.getCapacity() != null && examRoom.getCapacity() > room.getCapacity()) {
-                checkCapacity(examRoom, examRoom.getCapacity() - room.getCapacity());
+        try {
+            if (!examSiteLock.tryLock()) {
+                log.warn("[考场导入] 获取锁失败,同一个教学点不允许同时操作一个考点的容量修改, lockKey:{}", examSiteLockKey);
+                throw new StatusException("其他老师正在修改该考点的容量,不允许同时操作,请稍后再试!");
+            } else {
+                //保存之前的考点容量
+                ExamSiteEntity examSite = examSiteService.getById(room.getExamSiteId());
+                if (examSite == null) {
+                    throw new StatusException("找不到考点");
+                }
+                int oldCapacity = examSite.getCapacity();
+                Long roomId;
+
+                ExamRoomEntity examRoom = getExamRoom(room.getExamSiteId(), room.getCode());
+                if (examRoom != null) {
+
+                    // 校验容量
+                    if (examRoom.getCapacity() != null && room.getCapacity() != null && examRoom.getCapacity() > room.getCapacity()) {
+                        checkCapacity(examRoom, examRoom.getCapacity() - room.getCapacity());
+                    }
+
+                    roomId = examRoom.getId();
+                    examRoom.setCapacity(room.getCapacity());
+                    LambdaUpdateWrapper<ExamRoomEntity> wrapper = new LambdaUpdateWrapper<>();
+                    wrapper.set(ExamRoomEntity::getName, room.getName());
+                    wrapper.set(ExamRoomEntity::getAddress, room.getAddress());
+                    wrapper.set(ExamRoomEntity::getCapacity, room.getCapacity());
+                    wrapper.set(ExamRoomEntity::getEnable, Boolean.TRUE);
+                    wrapper.eq(ExamRoomEntity::getId, examRoom.getId());
+                    update(null, wrapper);
+                } else {
+                    save(room);
+                    roomId = room.getId();
+                }
+                //更新考点、教学点容量
+                updateExamSiteAndTeachingCapacity(room.getExamSiteId());
+                //更新之后的容量
+                examSite = examSiteService.getById(room.getExamSiteId());
+                //刷新缓存
+                refreshApplyCountCacheAfterCommit(roomId, room.getExamSiteId(), oldCapacity,  examSite.getCapacity());
+            }
+        } catch (Exception e) {
+            log.error("[考场导入]保存失败,原因:{}", e.getMessage(), e);
+            throw new StatusException(e.getMessage());
+        } finally {
+            try {
+                if (examSiteLock.isLocked() && examSiteLock.isHeldByCurrentThread()) {
+                    examSiteLock.unlock();
+                    log.info("[考场导入] 解锁成功,lockKey:{}", examSiteLockKey);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
             }
+        }
 
-            roomId = examRoom.getId();
-            examRoom.setCapacity(room.getCapacity());
-            LambdaUpdateWrapper<ExamRoomEntity> wrapper = new LambdaUpdateWrapper<>();
-            wrapper.set(ExamRoomEntity::getName, room.getName());
-            wrapper.set(ExamRoomEntity::getAddress, room.getAddress());
-            wrapper.set(ExamRoomEntity::getCapacity, room.getCapacity());
-            wrapper.set(ExamRoomEntity::getEnable, Boolean.TRUE);
-            wrapper.eq(ExamRoomEntity::getId, examRoom.getId());
-            update(null, wrapper);
-        } else {
-            save(room);
-            roomId = room.getId();
-        }
-        //更新考点、教学点容量
-        updateExamSiteAndTeachingCapacity(room.getExamSiteId());
-        //更新之后的容量
-        examSite = examSiteService.getById(room.getExamSiteId());
-        //刷新缓存
-        refreshApplyCountCacheAfterCommit(roomId, room.getExamSiteId(), oldCapacity,  examSite.getCapacity());
     }
 
     private void refreshApplyCountCacheAfterCommit(Long examRoomId, Long examSiteId, int oldCapacity, int newCapacity) {

+ 167 - 150
src/main/java/com/qmth/exam/reserve/service/impl/TimePeriodExamRoomServiceImpl.java

@@ -18,7 +18,6 @@ import com.qmth.exam.reserve.cache.impl.ExamSiteCacheService;
 import com.qmth.exam.reserve.cache.impl.OrgCacheService;
 import com.qmth.exam.reserve.dao.TimePeriodExamRoomDao;
 import com.qmth.exam.reserve.entity.ExamRoomEntity;
-import com.qmth.exam.reserve.entity.ExamSiteEntity;
 import com.qmth.exam.reserve.entity.TimePeriodEntity;
 import com.qmth.exam.reserve.entity.TimePeriodExamRoomEntity;
 import com.qmth.exam.reserve.enums.Role;
@@ -27,6 +26,7 @@ import com.qmth.exam.reserve.util.DateUtil;
 import com.qmth.exam.reserve.util.UnionUtil;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RLock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -210,178 +210,195 @@ public class TimePeriodExamRoomServiceImpl extends ServiceImpl<TimePeriodExamRoo
             throw new StatusException("保存失败,时间段列表为空");
         }
 
-        try {
-            // 获取当前机构
-            OrgInfo org = orgCacheService.currentOrg();
-            if (org == null) {
-                log.warn("[考场排班设置]未找到当前机构");
-                throw new StatusException("保存失败,未找到当前机构");
-            }
+        // 获取当前机构
+        OrgInfo org = orgCacheService.currentOrg();
+        if (org == null) {
+            log.warn("[考场排班设置]未找到当前机构");
+            throw new StatusException("保存失败,未找到当前机构");
+        }
 
-            // 获取当前任务
-            CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
-            if (curApplyTask == null) {
-                log.warn("[考场排班设置]机构:{},当前未有启用的任务", org.getOrgId());
-                throw new StatusException("保存失败,未有启用的任务");
-            }
+        // 获取当前任务
+        CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
+        if (curApplyTask == null) {
+            log.warn("[考场排班设置]机构:{},当前未有启用的任务", org.getOrgId());
+            throw new StatusException("保存失败,未有启用的任务");
+        }
 
-            // 校验考场信息
-            ExamRoomEntity examRoom = examRoomService.getById(examRoomId);
-            if (examRoom == null || !examRoom.getEnable()) {
-                log.warn("[考场排班设置]未找到或已被禁用的考场:{}", examRoomId);
-                throw new StatusException("保存失败,考场不存在或已被禁用");
-            }
+        // 校验考场信息
+        ExamRoomEntity examRoom = examRoomService.getById(examRoomId);
+        if (examRoom == null || !examRoom.getEnable()) {
+            log.warn("[考场排班设置]未找到或已被禁用的考场:{}", examRoomId);
+            throw new StatusException("保存失败,考场不存在或已被禁用");
+        }
 
-            // 校验考点信息
-            ExamSiteCacheBean examSiteCacheBean = examSiteCacheService.getExamSiteById(examRoom.getExamSiteId());
-            if (examSiteCacheBean == null) {
-                log.warn("[考场排班设置]未找到考点:{}", examRoom.getExamSiteId());
-                throw new StatusException("保存失败,未找到考点");
-            }
+        // 校验考点信息
+        ExamSiteCacheBean examSiteCacheBean = examSiteCacheService.getExamSiteById(examRoom.getExamSiteId());
+        if (examSiteCacheBean == null) {
+            log.warn("[考场排班设置]未找到考点:{}", examRoom.getExamSiteId());
+            throw new StatusException("保存失败,未找到考点");
+        }
 
-            String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, examRoom.getExamSiteId());
-            if (concurrentService.isLocked(lockKey)) {
-                log.warn("[考场排班设置]考点剩余可约数量更新中,不允许操作修改!lockKey:{}", lockKey);
-                throw new StatusException("系统正在更新可预约数量,不允许保存");
-            }
+        String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, examRoom.getExamSiteId());
+        if (concurrentService.isLocked(lockKey)) {
+            log.warn("[考场排班设置]考点剩余可约数量更新中,不允许操作修改!lockKey:{}", lockKey);
+            throw new StatusException("系统正在更新可预约数量,不允许保存");
+        }
 
-            // 获取已有时段数据
-            List<TimePeriodExamSiteBean> existingTimePeriods = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId);
-            Map<Long, TimePeriodExamSiteBean> existingMap = existingTimePeriods.stream()
-                    .collect(Collectors.toMap(TimePeriodExamSiteBean::getId, t -> t));
+        //考点容量变更锁
+        String examSiteLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CHANGE_CAPACITY, examRoom.getExamSiteId());
+        RLock examSiteLock = (RLock) concurrentService.getLock(examSiteLockKey);
 
-            // 过滤出可编辑的数据
-            List<TimePeriodExamSiteReq> editableList = timePeriodExamRoomList.stream()
-                    .filter(TimePeriodExamSiteReq::getEditable)
-                    .collect(Collectors.toList());
+        try {
+            if (!examSiteLock.tryLock()) {
+                log.warn("[考场排班设置] 获取锁失败,同一个教学点不允许同时操作一个考点的容量修改, lockKey:{}", examSiteLockKey);
+                throw new StatusException("其他老师正在修改该考点的容量,请稍后重试!");
+            } else {
+                // 获取已有时段数据
+                List<TimePeriodExamSiteBean> existingTimePeriods = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId);
+                Map<Long, TimePeriodExamSiteBean> existingMap = existingTimePeriods.stream()
+                        .collect(Collectors.toMap(TimePeriodExamSiteBean::getId, t -> t));
+
+                // 过滤出可编辑的数据
+                List<TimePeriodExamSiteReq> editableList = timePeriodExamRoomList.stream()
+                        .filter(TimePeriodExamSiteReq::getEditable)
+                        .collect(Collectors.toList());
 
-            if (editableList.isEmpty()) {
-                log.warn("[考场排班设置]无可编辑数据");
-                return;
-            }
+                if (editableList.isEmpty()) {
+                    log.warn("[考场排班设置]无可编辑数据");
+                    return;
+                }
+
+                // 构建实体列表
+                List<TimePeriodExamRoomEntity> entityList = editableList.stream()
+                        .map(req -> createTimePeriodExamRoomEntity(req, examRoomId, userId))
+                        .collect(Collectors.toList());
 
-            // 构建实体列表
-            List<TimePeriodExamRoomEntity> entityList = editableList.stream()
-                    .map(req -> createTimePeriodExamRoomEntity(req, examRoomId, userId))
-                    .collect(Collectors.toList());
+                // 分离新增和更新
+                List<TimePeriodExamRoomEntity> toBeSaved = new ArrayList<>();
+                List<TimePeriodExamRoomEntity> toBeUpdated = new ArrayList<>();
 
-            // 分离新增和更新
-            List<TimePeriodExamRoomEntity> toBeSaved = new ArrayList<>();
-            List<TimePeriodExamRoomEntity> toBeUpdated = new ArrayList<>();
+                entityList.forEach(e -> {
+                    if (e.getId() == null) {
+                        toBeSaved.add(e);
+                    } else {
+                        toBeUpdated.add(e);
+                    }
+                });
+
+                // 检查是否可编辑
+                List<Long> timePeriodIdsToEdit = new ArrayList<>();
+                toBeSaved.forEach(e -> timePeriodIdsToEdit.add(e.getTimePeriodId()));
+                toBeUpdated.forEach(e -> {
+                    TimePeriodExamSiteBean old = existingMap.get(e.getId());
+                    if (old != null && !old.getEnable().equals(e.getEnable())) {
+                        timePeriodIdsToEdit.add(e.getTimePeriodId());
+                    }
+                });
+                canEdit(timePeriodIdsToEdit, curApplyTask);
+
+                // 待更新、保存时段ID
+                Set<Long> updatedTimePeriodIds = new HashSet<>();
+                toBeSaved.forEach(e -> updatedTimePeriodIds.add(e.getTimePeriodId()));
+                toBeUpdated.forEach(e -> updatedTimePeriodIds.add(e.getTimePeriodId()));
+
+                // 保存前先记录旧容量
+                Map<Long, Integer> oldCapacityMap = new HashMap<>();
+                for (Long timePeriodId : updatedTimePeriodIds) {
+                    int capacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodId);
+                    oldCapacityMap.put(timePeriodId, capacity);
+                }
 
-            entityList.forEach(e -> {
-                if (e.getId() == null) {
-                    toBeSaved.add(e);
-                } else {
-                    toBeUpdated.add(e);
+                List<TimePeriodExamRoomEntity> verifyList = new ArrayList<>();
+                //只处理禁用的时段
+                List<TimePeriodExamRoomEntity> newTimePeriodList = toBeSaved.stream().filter(item -> !item.getEnable()).collect(Collectors.toList());
+                if (!newTimePeriodList.isEmpty()) {
+                    verifyList.addAll(newTimePeriodList);
                 }
-            });
-
-            // 检查是否可编辑
-            List<Long> timePeriodIdsToEdit = new ArrayList<>();
-            toBeSaved.forEach(e -> timePeriodIdsToEdit.add(e.getTimePeriodId()));
-            toBeUpdated.forEach(e -> {
-                TimePeriodExamSiteBean old = existingMap.get(e.getId());
-                if (old != null && !old.getEnable().equals(e.getEnable())) {
-                    timePeriodIdsToEdit.add(e.getTimePeriodId());
+                if (!toBeUpdated.isEmpty()) {
+                    verifyList.addAll(toBeUpdated);
                 }
-            });
-            canEdit(timePeriodIdsToEdit, curApplyTask);
-
-            // 待更新、保存时段ID
-            Set<Long> updatedTimePeriodIds = new HashSet<>();
-            toBeSaved.forEach(e -> updatedTimePeriodIds.add(e.getTimePeriodId()));
-            toBeUpdated.forEach(e -> updatedTimePeriodIds.add(e.getTimePeriodId()));
-
-            // 保存前先记录旧容量
-            Map<Long, Integer> oldCapacityMap = new HashMap<>();
-            for (Long timePeriodId : updatedTimePeriodIds) {
-                int capacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodId);
-                oldCapacityMap.put(timePeriodId, capacity);
-            }
 
-            List<TimePeriodExamRoomEntity> verifyList = new ArrayList<>();
-            //只处理禁用的时段
-            List<TimePeriodExamRoomEntity> newTimePeriodList = toBeSaved.stream().filter(item -> !item.getEnable()).collect(Collectors.toList());
-            if (!newTimePeriodList.isEmpty()) {
-                verifyList.addAll(newTimePeriodList);
-            }
-            if (!toBeUpdated.isEmpty()) {
-                verifyList.addAll(toBeUpdated);
-            }
-
-            //容量验证
-            int availableCount, haveApplyCount, oldCount;
-            for (TimePeriodExamRoomEntity toUpdate : verifyList) {
-                TimePeriodExamSiteBean bean;
-                if (toUpdate.getId() == null) {
-                    bean = new TimePeriodExamSiteBean();
-                    bean.setEnable(toUpdate.getEnable());
-                    bean.setTimePeriodId(toUpdate.getTimePeriodId());
-                } else {
-                    bean = existingMap.get(toUpdate.getId());
-                }
-                //只处理从开启到关闭的时段
-                if (bean != null && (bean.getId() == null || (bean.getEnable() && !toUpdate.getEnable()))) {
-                    // 剩余的容量,从缓存中获取
-                    availableCount = applyTaskCacheService.getApplyAvailableCount(examSiteCacheBean.getExamSiteId(), toUpdate.getTimePeriodId());
-                    // 修改之前的容量
-                    oldCount = oldCapacityMap.getOrDefault(toUpdate.getTimePeriodId(), 0);
-                    // 已预约的容量
-                    haveApplyCount = oldCount - availableCount;
-                    // 关闭之后的剩余容量
-                    int remainCount = oldCount - examRoom.getCapacity();
-
-                    log.warn("haveApplyCount:{}, remainCount:{}", haveApplyCount, remainCount);
-
-                    if (haveApplyCount > remainCount) {
-                        TimePeriodEntity timePeriod = timePeriodService.getById(toUpdate.getTimePeriodId());
-                        String dateStr = DateUtil.getShortDateByLongTime(timePeriod.getStartTime());
-                        String timeStr = DateUtil.getStartToEndTime(timePeriod.getStartTime(), timePeriod.getEndTime());
-                        String errorMessage = String.format("时段:%s %s,已预约%d人,当前已预约人数超出剩余容量%d人,无法关闭。", dateStr, timeStr, haveApplyCount,
-                                remainCount);
-                        log.error(errorMessage);
-                        throw new StatusException(errorMessage);
+                //容量验证
+                int availableCount, haveApplyCount, oldCount;
+                for (TimePeriodExamRoomEntity toUpdate : verifyList) {
+                    TimePeriodExamSiteBean bean;
+                    if (toUpdate.getId() == null) {
+                        bean = new TimePeriodExamSiteBean();
+                        bean.setEnable(toUpdate.getEnable());
+                        bean.setTimePeriodId(toUpdate.getTimePeriodId());
+                    } else {
+                        bean = existingMap.get(toUpdate.getId());
+                    }
+                    //只处理从开启到关闭的时段
+                    if (bean != null && (bean.getId() == null || (bean.getEnable() && !toUpdate.getEnable()))) {
+                        // 剩余的容量,从缓存中获取
+                        availableCount = applyTaskCacheService.getApplyAvailableCount(examSiteCacheBean.getExamSiteId(), toUpdate.getTimePeriodId());
+                        // 修改之前的容量
+                        oldCount = oldCapacityMap.getOrDefault(toUpdate.getTimePeriodId(), 0);
+                        // 已预约的容量
+                        haveApplyCount = oldCount - availableCount;
+                        // 关闭之后的剩余容量
+                        int remainCount = oldCount - examRoom.getCapacity();
+
+                        log.warn("haveApplyCount:{}, remainCount:{}", haveApplyCount, remainCount);
+
+                        if (haveApplyCount > remainCount) {
+                            TimePeriodEntity timePeriod = timePeriodService.getById(toUpdate.getTimePeriodId());
+                            String dateStr = DateUtil.getShortDateByLongTime(timePeriod.getStartTime());
+                            String timeStr = DateUtil.getStartToEndTime(timePeriod.getStartTime(), timePeriod.getEndTime());
+                            String errorMessage = String.format("时段:%s %s,已预约%d人,当前已预约人数超出剩余容量%d人,无法关闭。", dateStr, timeStr, haveApplyCount,
+                                    remainCount);
+                            log.error(errorMessage);
+                            throw new StatusException(errorMessage);
+                        }
                     }
                 }
-            }
 
-            // 批量保存或更新
-            if (!toBeSaved.isEmpty()) {
-                //防止重复保存
-                checkExistTimePeriodExamRoom(toBeSaved, Collections.emptyList());
-                saveBatch(toBeSaved);
-            }
-
-            if (!toBeUpdated.isEmpty()) {
-                List<Long> ids = toBeUpdated.stream()
-                        .map(TimePeriodExamRoomEntity::getId)
-                        .collect(Collectors.toList());
-                checkExistTimePeriodExamRoom(toBeUpdated, ids);
+                // 批量保存或更新
+                if (!toBeSaved.isEmpty()) {
+                    //防止重复保存
+                    checkExistTimePeriodExamRoom(toBeSaved, Collections.emptyList());
+                    saveBatch(toBeSaved);
+                }
 
-                updateBatchById(toBeUpdated);
-            }
+                if (!toBeUpdated.isEmpty()) {
+                    List<Long> ids = toBeUpdated.stream()
+                            .map(TimePeriodExamRoomEntity::getId)
+                            .collect(Collectors.toList());
+                    checkExistTimePeriodExamRoom(toBeUpdated, ids);
 
-            // 提交事务后刷新缓存
-            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-                @Override
-                public void afterCommit() {
-                    for (Long timePeriodId : updatedTimePeriodIds) {
-                        int oldCapacity = oldCapacityMap.getOrDefault(timePeriodId, 0);;
-                        int newCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodId);
-                        applyTaskCacheService.refreshApplyAvailableCountCache(
-                                examRoom.getExamSiteId(),
-                                timePeriodId,
-                                oldCapacity,
-                                newCapacity
-                        );
-                    }
+                    updateBatchById(toBeUpdated);
                 }
-            });
 
+                // 提交事务后刷新缓存
+                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+                    @Override
+                    public void afterCommit() {
+                        for (Long timePeriodId : updatedTimePeriodIds) {
+                            int oldCapacity = oldCapacityMap.getOrDefault(timePeriodId, 0);;
+                            int newCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodId);
+                            applyTaskCacheService.refreshApplyAvailableCountCache(
+                                    examRoom.getExamSiteId(),
+                                    timePeriodId,
+                                    oldCapacity,
+                                    newCapacity
+                            );
+                        }
+                    }
+                });
+            }
         } catch (Exception e) {
             log.error("[考场排班设置]保存失败,原因:{}", e.getMessage(), e);
             throw new StatusException(e.getMessage());
+        } finally {
+            try {
+                if (examSiteLock.isLocked() && examSiteLock.isHeldByCurrentThread()) {
+                    examSiteLock.unlock();
+                    log.info("[考场排班设置] 解锁成功,lockKey:{}", examSiteLockKey);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
+            }
         }
     }