haogh 1 月之前
父節點
當前提交
ed40da789f

+ 71 - 41
src/main/java/com/qmth/exam/reserve/service/impl/ExamRoomServiceImpl.java

@@ -26,7 +26,6 @@ import com.qmth.exam.reserve.service.*;
 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;
@@ -34,12 +33,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 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 java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -78,24 +76,31 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
     @Transactional
     @Override
     public void saveExamRoom(LoginUser user, ExamRoomSaveReq req) {
+        if (req.getExamSiteId() == null) {
+            throw new StatusException("考点ID不能为空");
+        }
+
+        // 锁检查
         if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
             log.warn("[考场保存]系统自动预约中,不允许修改考场!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
             throw new StatusException("系统正在自动预约中,不允许修改考场");
         }
         String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, req.getExamSiteId());
         if (concurrentService.isLocked(lockKey)) {
-            log.warn("[考场保存]考点剩余可约数量更新中,不允许考生操作修改!lockKey:{}", lockKey);
+            log.warn("[考场保存]考点剩余可约数量更新中,不允许操作修改!lockKey:{}", lockKey);
             throw new StatusException("系统正在更新可预约数量,不允许修改考场");
         }
 
         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 = 0;
+        int newCapacity;
         if (req.getId() == null) {
             examRoomEntity.setEnable(Boolean.TRUE);
             save(examRoomEntity);
@@ -106,15 +111,16 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
         //更新考点容量和教学点容量
         updateExamSiteAndTeachingCapacity(req.getExamSiteId());
         //考场更换考点
-        if(req.getId() != null && !req.getExamSiteId().equals(beforeUpdateRoom.getExamSiteId())) {
+        if (req.getId() != null && !req.getExamSiteId().equals(beforeUpdateRoom.getExamSiteId())) {
             updateExamSiteAndTeachingCapacity(beforeUpdateRoom.getExamSiteId());
         }
 
+        // 获取更新后的容量
         examSite = examSiteService.getById(req.getExamSiteId());
         newCapacity = examSite.getCapacity();
 
         //刷新缓存
-        refreshApplyCountCache(examRoomEntity.getId(), examRoomEntity.getExamSiteId(), oldCapacity, newCapacity);
+        doRefreshApplyCountCache(examRoomEntity.getId(), examRoomEntity.getExamSiteId(), oldCapacity, newCapacity);
     }
 
 
@@ -123,13 +129,13 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
     @Override
     public void enable(Long id, Boolean enable) {
         if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
-            log.warn("[考场禁用/启用]系统自动预约中,不允许修改考场!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+            log.warn("[考场禁用/启用]系统自动预约中,不允许启用/禁用考场!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
             throw new StatusException("系统正在自动预约中,不允许启用/禁用考场");
         }
         ExamRoomEntity examRoom = getById(id);
         String lockKey = String.format(CacheConstants.LOCK_EXAM_SITE_CAPACITY, examRoom.getExamSiteId());
         if (concurrentService.isLocked(lockKey)) {
-            log.warn("[考场保存]考点剩余可约数量更新中,不允许考生操作修改!lockKey:{}", lockKey);
+            log.warn("[考场保存]考点剩余可约数量更新中,不允许启用/禁用考场!lockKey:{}", lockKey);
             throw new StatusException("系统正在更新可预约数量,不允许启用/禁用考场");
         }
 
@@ -148,13 +154,14 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
         examSite = examSiteService.getById(examRoom.getExamSiteId());
 
         //刷新缓存
-        refreshApplyCountCache(id, examRoom.getExamSiteId(), oldCapacity, examSite.getCapacity());
+        doRefreshApplyCountCache(id, examRoom.getExamSiteId(), oldCapacity, examSite.getCapacity());
     }
 
+    @Transactional
     @Override
     public List<Map<String, Object>> importExamRoom(LoginUser user, InputStream inputStream) {
         if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
-            log.warn("[考场导入]系统自动预约中,不允许修改考场!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+            log.warn("[考场导入]系统自动预约中,不允许导入考场!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
             throw new StatusException("系统正在自动预约中,不允许导入考场");
         }
 
@@ -245,7 +252,7 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
             try {
                 saveRoom(examRoomEntity);
             } catch (Exception e) {
-                failRecords.add(newError(i + 1, " 系统异常"));
+                failRecords.add(newError(i + 1, " " + e.getMessage()));
                 log.error("导入异常", e);
             }
         }
@@ -275,19 +282,21 @@ 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;
+
         ExamRoomEntity examRoom = getExamRoom(room.getExamSiteId(), room.getCode());
-        int oldCapacity = 0;
-        ExamSiteEntity examSite;
         if (examRoom != null) {
-            examSite = examSiteService.getById(examRoom.getExamSiteId());
-            if (examSite == null) {
-                throw new StatusException("找不到考点");
-            }
-            oldCapacity = examSite.getCapacity();
+            roomId = examRoom.getId();
             examRoom.setCapacity(room.getCapacity());
             LambdaUpdateWrapper<ExamRoomEntity> wrapper = new LambdaUpdateWrapper<>();
             wrapper.set(ExamRoomEntity::getName, room.getName());
@@ -298,15 +307,54 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
             update(null, wrapper);
         } else {
             save(room);
+            roomId = room.getId();
         }
         //更新考点、教学点容量
         updateExamSiteAndTeachingCapacity(room.getExamSiteId());
         //更新之后的容量
         examSite = examSiteService.getById(room.getExamSiteId());
         //刷新缓存
-        refreshApplyCountCache(room.getId(), room.getExamSiteId(), oldCapacity, examSite.getCapacity());
+        refreshApplyCountCacheAfterCommit(roomId, room.getExamSiteId(), oldCapacity,  examSite.getCapacity());
     }
 
+    private void refreshApplyCountCacheAfterCommit(Long examRoomId, Long examSiteId, int oldCapacity, int newCapacity) {
+        if (TransactionSynchronizationManager.isActualTransactionActive()) {
+            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
+                @Override
+                public void afterCommit() {
+                    doRefreshApplyCountCache(examRoomId, examSiteId, oldCapacity, newCapacity);
+                }
+            });
+        } else {
+            doRefreshApplyCountCache(examRoomId, examSiteId, oldCapacity, newCapacity);
+        }
+    }
+
+    private void doRefreshApplyCountCache(Long examRoomId, Long examSiteId, int oldCapacity, int newCapacity) {
+        List<TimePeriodEntity> timePeriods = timePeriodService.list(
+                new LambdaQueryWrapper<TimePeriodEntity>().select(TimePeriodEntity::getId));
+
+        List<TimePeriodExamRoomEntity> timePeriodExamRooms = timePeriodExamRoomService.listExamRoom(examRoomId);
+
+        Map<Long, TimePeriodExamRoomEntity> timePeriodExamRoomMap = Optional.ofNullable(timePeriodExamRooms)
+                .orElse(Collections.emptyList())
+                .stream()
+                .collect(Collectors.toMap(
+                        TimePeriodExamRoomEntity::getTimePeriodId,
+                        Function.identity(),
+                        (existing, replacement) -> existing
+                ));
+
+        for (TimePeriodEntity timePeriod : timePeriods) {
+            TimePeriodExamRoomEntity timePeriodExamRoomEntity = timePeriodExamRoomMap.get(timePeriod.getId());
+            if (timePeriodExamRoomEntity == null || timePeriodExamRoomEntity.getEnable()) {
+                cacheService.refreshApplyAvailableCountCache(examSiteId, timePeriod.getId(), oldCapacity, newCapacity);
+            }
+        }
+    }
+
+
     private List<ExamSiteEntity> listExamSite(String examSiteCode, String examSiteName, Long categoryId) {
         LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(ExamSiteEntity::getCode, examSiteCode);
@@ -323,24 +371,6 @@ public class ExamRoomServiceImpl extends ServiceImpl<ExamRoomDao, ExamRoomEntity
         categoryService.updateTeachingCapacity(newExamSite.getCategoryId());
     }
 
-    private void refreshApplyCountCache(Long examRoomId,Long examSiteId,  int oldCapacity, int newCapacity) {
-        List<TimePeriodEntity> timePeriods = timePeriodService.list(new LambdaQueryWrapper<TimePeriodEntity>().select(TimePeriodEntity::getId));
-        //考场时段列表
-        List<TimePeriodExamRoomEntity> timePeriodExamRooms = timePeriodExamRoomService.listExamRoom(examRoomId);
-        //将timePeriodExamRooms转Map
-        Map<Long, TimePeriodExamRoomEntity> timePeriodExamRoomMap = timePeriodExamRooms.stream().collect(
-                Collectors.toMap(TimePeriodExamRoomEntity::getTimePeriodId, Function.identity()));
-
-        for (TimePeriodEntity timePeriod : timePeriods) {
-            //刷新缓存
-            TimePeriodExamRoomEntity timePeriodExamRoomEntity = timePeriodExamRoomMap.get(timePeriod.getId());
-            if (timePeriodExamRoomEntity == null || timePeriodExamRoomEntity.getEnable()) {
-                cacheService.refreshApplyAvailableCountCache(examSiteId, timePeriod.getId(), oldCapacity, newCapacity);
-            }
-
-        }
-    }
-
     private void checkExamRoom(ExamRoomSaveReq req) {
         if (StringUtils.isEmpty(req.getCode())) {
             throw new StatusException("考场代码不能为空");

+ 71 - 112
src/main/java/com/qmth/exam/reserve/service/impl/TimePeriodExamRoomServiceImpl.java

@@ -29,14 +29,14 @@ 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;
 import org.springframework.beans.factory.annotation.Autowired;
 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 java.util.*;
 import java.util.stream.Collectors;
@@ -196,7 +196,7 @@ public class TimePeriodExamRoomServiceImpl extends ServiceImpl<TimePeriodExamRoo
         return !(endTime < now || startTime < longOtherDay);
     }
 
-    @Transactional(isolation = Isolation.READ_COMMITTED)
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public void save(Long userId, Long examRoomId, List<TimePeriodExamSiteReq> timePeriodExamRoomList) {
         if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) {
@@ -226,15 +226,10 @@ public class TimePeriodExamRoomServiceImpl extends ServiceImpl<TimePeriodExamRoo
 
             // 校验考场信息
             ExamRoomEntity examRoom = examRoomService.getById(examRoomId);
-            if (examRoom == null) {
-                log.warn("[考场排班设置]未找到考场:{}", examRoomId);
-                throw new StatusException("保存失败,未找到考场");
+            if (examRoom == null || !examRoom.getEnable()) {
+                log.warn("[考场排班设置]未找到或已被禁用的考场:{}", examRoomId);
+                throw new StatusException("保存失败,考场不存在或已被禁用");
             }
-            if (!examRoom.getEnable()) {
-                log.warn("[考场排班设置]考场已被禁用:{}", examRoomId);
-                throw new StatusException("保存失败,考场已被禁用");
-            }
-
 
             // 校验考点信息
             ExamSiteCacheBean examSiteCacheBean = examSiteCacheService.getExamSiteById(examRoom.getExamSiteId());
@@ -249,122 +244,86 @@ public class TimePeriodExamRoomServiceImpl extends ServiceImpl<TimePeriodExamRoo
                 throw new StatusException("系统正在更新可预约数量,不允许保存");
             }
 
-            // 获取考场对应的所有时段
-            List<TimePeriodExamSiteBean> timePeriodList = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId);
-
-            // 时段Map
-            Map<Long, TimePeriodExamSiteBean> timePeriodMap = timePeriodList.stream()
-                    .collect(Collectors.toMap(TimePeriodExamSiteBean::getId, tp -> tp));
-
-            //只处理可编辑的数据
-            timePeriodExamRoomList = timePeriodExamRoomList.stream().filter(TimePeriodExamSiteReq::getEditable).collect(Collectors.toList());
-
-            List<TimePeriodExamRoomEntity> timePeriodExamRoomEntityList = new ArrayList<>(timePeriodExamRoomList.size());
-
-            if (CollectionUtils.isEmpty(timePeriodList)) {
-                //是否可编辑判断
-                List<Long> listTimePeriodIds = timePeriodExamRoomList.stream()
-                        .filter(item -> !item.getEnable()) //禁用
-                        .map(TimePeriodExamSiteReq::getTimePeriodId)
-                        .collect(Collectors.toList());
-                canEdit(listTimePeriodIds, curApplyTask);
-
-                timePeriodExamRoomList.forEach(item -> {
-                    TimePeriodExamRoomEntity entity = createTimePeriodExamRoomEntity(item, examRoom.getId(), userId);
-                    timePeriodExamRoomEntityList.add(entity);
-                });
-
-                //批量保存
-                //saveBatch(timePeriodExamRoomEntityList);
-                //刷新缓存
-                //refreshDisableTimePeriodCache(timePeriodExamRoomEntityList, examRoom);
-
-                int oldCapacity;
-                int newCapacity;
-                for(TimePeriodExamRoomEntity timePeriodExamRoomEntity: timePeriodExamRoomEntityList) {
-                    //保存之前容量
-                    oldCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodExamRoomEntity.getTimePeriodId());
-                    //保存
-                    save(timePeriodExamRoomEntity);
-                    //保存之后容量
-                    newCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), timePeriodExamRoomEntity.getTimePeriodId());
-
-                    // 更新容量缓存,禁用需要减考场容量
-                    applyTaskCacheService.refreshApplyAvailableCountCache(
-                            examRoom.getExamSiteId(),
-                            timePeriodExamRoomEntity.getTimePeriodId(),
-                            oldCapacity,
-                            newCapacity
-                    );
-                }
+            // 获取已有时段数据
+            List<TimePeriodExamSiteBean> existingTimePeriods = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId);
+            Map<Long, TimePeriodExamSiteBean> existingMap = existingTimePeriods.stream()
+                    .collect(Collectors.toMap(TimePeriodExamSiteBean::getId, t -> t));
 
-            } else { //非第一次保存
-                List<TimePeriodExamRoomEntity> toBeSaveList = new ArrayList<>();
-                List<TimePeriodExamRoomEntity> toBeUpdateList = new ArrayList<>();
+            // 过滤出可编辑的数据
+            List<TimePeriodExamSiteReq> editableList = timePeriodExamRoomList.stream()
+                    .filter(TimePeriodExamSiteReq::getEditable)
+                    .collect(Collectors.toList());
 
-                timePeriodExamRoomList.forEach(item -> {
-                    TimePeriodExamRoomEntity entity = createTimePeriodExamRoomEntity(item, examRoom.getId(), userId);
-                    timePeriodExamRoomEntityList.add(entity);
+            if (editableList.isEmpty()) {
+                log.warn("[考场排班设置]无可编辑数据");
+                return;
+            }
 
-                    if (item.getId() == null) {
-                        toBeSaveList.add(entity);
-                    } else {
-                        toBeUpdateList.add(entity);
-                    }
-                });
+            // 构建实体列表
+            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<Long> resultList = new ArrayList<>();
-                for(TimePeriodExamRoomEntity item : toBeSaveList) {
-                    resultList.add(item.getTimePeriodId());
+            entityList.forEach(e -> {
+                if (e.getId() == null) {
+                    toBeSaved.add(e);
+                } else {
+                    toBeUpdated.add(e);
                 }
-                //更新的记录,值有变化
-                for (TimePeriodExamRoomEntity item : toBeUpdateList) {
-                    TimePeriodExamSiteBean timePeriod = timePeriodMap.get(item.getId());
-                    if(timePeriod != null && !timePeriod.getEnable().equals(item.getEnable())) {
-                        resultList.add(item.getTimePeriodId());
-                    }
+            });
+
+            // 检查是否可编辑
+            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);
+            }
+
+            // 批量保存或更新
+            if (!toBeSaved.isEmpty()) {
+                saveBatch(toBeSaved);
+            }
+            if (!toBeUpdated.isEmpty()) {
+                updateBatchById(toBeUpdated);
+            }
 
-                canEdit(resultList, curApplyTask);
-
-                //批量保存更新
-                //saveOrUpdateBatch(timePeriodExamRoomEntityList);
-
-                int oldCapacity;
-                int newCapacity;
-                for (TimePeriodExamRoomEntity item : timePeriodExamRoomEntityList) {
-                    if (item.getId() != null) {
-                        TimePeriodExamSiteBean timePeriod = timePeriodMap.get(item.getId());
-                        if(timePeriod != null && !timePeriod.getEnable().equals(item.getEnable())) {
-                            oldCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), item.getTimePeriodId());
-                            updateById(item);
-                            newCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), item.getTimePeriodId());
-                            //刷新缓存中的容量
-                            applyTaskCacheService.refreshApplyAvailableCountCache(
-                                    examRoom.getExamSiteId(),
-                                    item.getTimePeriodId(),
-                                    oldCapacity,
-                                    newCapacity
-                            );
-                        } else {
-                            updateById(item);
-                        }
-                    } else {
-                        oldCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), item.getTimePeriodId());
-                        save(item);
-                        newCapacity = examSiteService.getExamSiteTimePeriodCapacity(examSiteCacheBean.getExamSiteId(), item.getTimePeriodId());
-                        //刷新缓存中的容量
+            // 提交事务后刷新缓存
+            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(),
-                                item.getTimePeriodId(),
+                                timePeriodId,
                                 oldCapacity,
                                 newCapacity
                         );
                     }
                 }
+            });
 
-            }
         } catch (Exception e) {
             log.error("[考场排班设置]保存失败,原因:{}", e.getMessage(), e);
             throw new StatusException(e.getMessage());