|
@@ -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());
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|