package com.qmth.exam.reserve.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qmth.boot.core.concurrent.service.ConcurrentService; import com.qmth.boot.core.exception.StatusException; import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO; import com.qmth.exam.reserve.bean.examsite.ExamSiteCacheBean; import com.qmth.exam.reserve.bean.login.LoginUser; import com.qmth.exam.reserve.bean.org.OrgInfo; import com.qmth.exam.reserve.bean.timeperiod.TimePeriodExamSiteBean; import com.qmth.exam.reserve.bean.timeperiod.TimePeriodExamSiteInfo; import com.qmth.exam.reserve.bean.timeperiod.TimePeriodExamSiteReq; import com.qmth.exam.reserve.bean.timeperiod.TimePeriodExamSiteVo; import com.qmth.exam.reserve.cache.CacheConstants; import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService; 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.TimePeriodEntity; import com.qmth.exam.reserve.entity.TimePeriodExamRoomEntity; import com.qmth.exam.reserve.enums.Role; import com.qmth.exam.reserve.service.ExamRoomService; import com.qmth.exam.reserve.service.ExamSiteService; import com.qmth.exam.reserve.service.TimePeriodExamRoomService; import com.qmth.exam.reserve.service.TimePeriodService; 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 java.util.*; import java.util.stream.Collectors; @Service public class TimePeriodExamRoomServiceImpl extends ServiceImpl implements TimePeriodExamRoomService { private static final Logger log = LoggerFactory.getLogger(TimePeriodExamRoomServiceImpl.class); @Autowired private ApplyTaskCacheService applyTaskCacheService; @Autowired private OrgCacheService orgCacheService; @Autowired private TimePeriodService timePeriodService; @Autowired private ExamSiteCacheService examSiteCacheService; @Autowired private ExamRoomService examRoomService; @Autowired private ConcurrentService concurrentService; @Autowired private ExamSiteService examSiteService; @Override public List ListDetail(LoginUser loginUser, Long examRoomId) { //获取当前机构 OrgInfo org = orgCacheService.currentOrg(); if (org == null) { log.warn("[考场排班设置列表]未找到当前机构"); return Collections.emptyList(); } //判断考场是否存在 ExamRoomEntity examRoom = examRoomService.getById(examRoomId); if (examRoom == null) { log.warn("[考场排班设置列表]未找到考场:{}", examRoomId); return Collections.emptyList(); } //教学点管理员权限限制 ExamSiteCacheBean examSite = examSiteCacheService.getExamSiteById(examRoom.getExamSiteId()); if (examSite == null) { log.warn("[考场排班设置列表]考场:{}未找到所属考点", examRoomId); return Collections.emptyList(); } if (loginUser.getRole().equals(Role.TEACHING) && !loginUser.getCategoryId().equals(examSite.getCategoryId())) { return Collections.emptyList(); } //获取当前任务 CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId()); if (curApplyTask == null) { log.warn("[考场排班设置列表]机构:{},当前未有启用的任务", org.getOrgId()); return Collections.emptyList(); } // 所有的预约日期 List dateList = timePeriodService.listTimePeriodDate(curApplyTask.getTaskId()); if (CollectionUtils.isEmpty(dateList)) { log.warn("[考场排班设置列表]当前任务:{}未设置预约日期", curApplyTask.getTaskId()); return Collections.emptyList(); } //教学点管理员设置的所有的考点时段 List timePeriodExamRoomList = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId); //学校管理员设置的所有预约时段 List timePeriodList = timePeriodService.listTimePeriodByTask(curApplyTask.getTaskId()); List resultList; if (CollectionUtils.isEmpty(timePeriodExamRoomList)) { resultList = timePeriodList; } else { //取并集 resultList = UnionUtil.unionByAttribute(timePeriodExamRoomList, timePeriodList, TimePeriodExamSiteBean::getTimePeriodId); } //按日期封装 List list = new ArrayList<>(); for (String date : dateList) { TimePeriodExamSiteVo timePeriodVo = new TimePeriodExamSiteVo(); timePeriodVo.setDateStr(getDateStr(date)); timePeriodVo.setTimePeriodList(filterTimePeriod(date, resultList, curApplyTask.getAllowApplyCancelDays())); list.add(timePeriodVo); } return list; } private String getDateStr(String date) { if (StringUtils.isEmpty(date)) { return ""; } String[] dateArr = date.split("-"); if (dateArr.length != 3) { return ""; } return dateArr[1] + "月" + dateArr[2] + "日"; } private List filterTimePeriod(String date, List timePeriodList, Integer canCancelDay) { // 参数校验 if (timePeriodList == null || date == null || canCancelDay == null || canCancelDay < 0) { return new ArrayList<>(); } List resultList = new ArrayList<>(); // 过滤符合条件的时间段 List filteredTimePeriodList = timePeriodList.stream() .filter(item -> DateUtil.getShortDateByLongTime(item.getStartTime()).equals(date)) .sorted(Comparator.comparing(TimePeriodExamSiteBean::getStartTime)) .collect(Collectors.toList()); // 计算可取消时间范围 Long longToday = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(new Date()) + " 00:00:00"); Date today = new Date(longToday); Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, canCancelDay); long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59"); // 当前时间 long now = System.currentTimeMillis(); for (TimePeriodExamSiteBean time : filteredTimePeriodList) { // 检查时间是否有效 if (time.getStartTime() == null || time.getEndTime() == null) { continue; // 跳过无效时间段 } TimePeriodExamSiteInfo bean = new TimePeriodExamSiteInfo(); bean.setId(time.getId()); bean.setTimePeriodId(time.getTimePeriodId()); bean.setEnable(time.getEnable()); bean.setTimePeriodStr(DateUtil.getStartToEndTime(time.getStartTime(), time.getEndTime())); // 判断是否可编辑 boolean isEditable = isTimeEditable(now, time.getEndTime(), longOtherDay, time.getStartTime()); bean.setEditable(isEditable); resultList.add(bean); } return resultList; } private boolean isTimeEditable(long now, long endTime, long longOtherDay, long startTime) { return !(endTime < now || startTime < longOtherDay); } @Transactional(isolation = Isolation.READ_COMMITTED) @Override public void save(Long userId, Long examRoomId, List timePeriodExamRoomList) { if (concurrentService.isLocked(CacheConstants.LOCK_AUTO_APPLY)) { log.warn("[考场排班保存]系统自动预约中,不允许操作考场排班!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY); throw new StatusException("系统正在自动预约中,不允许修改"); } if (CollectionUtils.isEmpty(timePeriodExamRoomList)) { log.warn("[考场排班设置]时间段列表为空"); throw new StatusException("保存失败,时间段列表为空"); } try { // 获取当前机构 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("保存失败,未有启用的任务"); } // 校验考场信息 ExamRoomEntity examRoom = examRoomService.getById(examRoomId); if (examRoom == null) { log.warn("[考场排班设置]未找到考场:{}", examRoomId); throw new StatusException("保存失败,未找到考场"); } if (!examRoom.getEnable()) { log.warn("[考场排班设置]考场已被禁用:{}", examRoomId); throw new StatusException("保存失败,考场已被禁用"); } // 校验考点信息 ExamSiteCacheBean examSiteCacheBean = examSiteCacheService.getExamSiteById(examRoom.getExamSiteId()); if (examSiteCacheBean == null) { log.warn("[考场排班设置]未找到考点:{}", examRoom.getExamSiteId()); throw new StatusException("保存失败,未找到考点"); } // 获取考场对应的所有时段 List timePeriodList = timePeriodService.listTimePeriodByExamRoomId(curApplyTask.getTaskId(), examRoomId); // 时段Map Map timePeriodMap = timePeriodList.stream() .collect(Collectors.toMap(TimePeriodExamSiteBean::getId, tp -> tp)); //只处理可编辑的数据 timePeriodExamRoomList = timePeriodExamRoomList.stream().filter(TimePeriodExamSiteReq::getEditable).collect(Collectors.toList()); List timePeriodExamRoomEntityList = new ArrayList<>(timePeriodExamRoomList.size()); if (CollectionUtils.isEmpty(timePeriodList)) { //是否可编辑判断 List listTimePeriodIds = timePeriodExamRoomList.stream() .filter(item -> !item.getEnable()) //禁用 .map(TimePeriodExamSiteReq::getTimePeriodId) .collect(Collectors.toList()); canEdit(listTimePeriodIds, curApplyTask); handleFirstTimeSave(userId, examRoomId, timePeriodExamRoomList, timePeriodExamRoomEntityList, examRoom); } else { handleUpdateOrSave(userId, examRoomId, timePeriodExamRoomList, timePeriodExamRoomEntityList, timePeriodMap, examRoom, curApplyTask); } } catch (Exception e) { log.error("[考场排班设置]保存失败,原因:{}", e.getMessage(), e); throw new StatusException(e.getMessage()); } } @Override public List listExamRoom(Long examSiteId, Long timePeriodId, Boolean enable) { return getBaseMapper().listExamRoom(examSiteId, timePeriodId, enable); } @Override public List listExamRoom(Long examRoomId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TimePeriodExamRoomEntity::getExamRoomId, examRoomId); return list(wrapper); } @Override public int getExamSiteTimePeriodCapacity(Long examSiteId, Long timePeriodId) { // 判断考点是否存在 ExamSiteCacheBean examSiteCacheBean = examSiteCacheService.getExamSiteById(examSiteId); if (examSiteCacheBean == null) { log.error("[获取考点时段可预约数量]考点不存在,examSiteId:{}", examSiteId); return 0; } // 判断时段是否存在 TimePeriodEntity timePeriod = timePeriodService.getById(timePeriodId); if (timePeriod == null) { log.error("[获取考点时段可预约数量]时段不存在,timePeriodId:{}", timePeriodId); return 0; } // 获取考点对应的所有考场 List examRoomList = examRoomService.listExamRoom(examSiteId); if (examRoomList.isEmpty()) { return 0; } // 获取所有考场ids List examRoomIds = examRoomList.stream() .map(ExamRoomEntity::getId) .collect(Collectors.toList()); List periodList = getBaseMapper().listByExamRoomIdsAndTimePeriodId(examRoomIds, timePeriodId); Map enableMap = new HashMap<>(); for (TimePeriodExamRoomEntity entity : periodList) { enableMap.put(entity.getExamRoomId(), entity.getEnable()); } // 计算启用状态下的考场总容量 return examRoomList.stream() .filter(examRoom -> { Boolean enabled = enableMap.get(examRoom.getId()); return enabled == null || enabled; // 默认启用 }) .mapToInt(ExamRoomEntity::getCapacity) .sum(); } private TimePeriodExamRoomEntity getTimePeriodExamRoomEntity(Long examRoomId, Long timePeriodId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TimePeriodExamRoomEntity::getExamRoomId, examRoomId); queryWrapper.eq(TimePeriodExamRoomEntity::getTimePeriodId, timePeriodId); return getOne(queryWrapper); } // 校验是否可编辑 private void canEdit(List timePeriodExamRoomList, CurrentApplyTaskVO curApplyTask) { if (CollectionUtils.isNotEmpty(timePeriodExamRoomList)) { Long longToday = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(new Date()) + " 00:00:00"); Date today = new Date(longToday); Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, curApplyTask.getAllowApplyCancelDays()); long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59"); // 当前时间 long now = System.currentTimeMillis(); List timePeriodEntities = timePeriodService.listByIds(timePeriodExamRoomList); Map timePeriodEntityMap = timePeriodEntities.stream() .collect(Collectors.toMap(TimePeriodEntity::getId, tpe -> tpe)); for (Long timePeriodId : timePeriodExamRoomList) { TimePeriodEntity timePeriod = timePeriodEntityMap.get(timePeriodId); if (!isTimeEditable(now, timePeriod.getEndTime(), longOtherDay, timePeriod.getStartTime())) { throw new StatusException("保存失败," + DateUtil.getShortDateByLongTime(timePeriod.getStartTime()) + "可编辑时间范围已过"); } } } } private void handleFirstTimeSave(Long userId, Long examRoomId, List timePeriodExamRoomList, List timePeriodExamRoomEntityList, ExamRoomEntity examRoom) { timePeriodExamRoomList.forEach(item -> { TimePeriodExamRoomEntity entity = createTimePeriodExamRoomEntity(item, examRoomId, userId); timePeriodExamRoomEntityList.add(entity); }); saveBatch(timePeriodExamRoomEntityList); //刷新缓存 refreshDisableTimePeriodCache(timePeriodExamRoomEntityList, examRoom); } private void handleUpdateOrSave(Long userId, Long examRoomId, List timePeriodExamRoomList, List timePeriodExamRoomEntityList, Map timePeriodMap, ExamRoomEntity examRoom, CurrentApplyTaskVO curApplyTask) { List toBeSaveList = new ArrayList<>(); List toBeUpdateList = new ArrayList<>(); timePeriodExamRoomList.forEach(item -> { TimePeriodExamRoomEntity entity = createTimePeriodExamRoomEntity(item, examRoomId, userId); timePeriodExamRoomEntityList.add(entity); if (item.getId() == null) { toBeSaveList.add(entity); } else { toBeUpdateList.add(entity); } }); //新增的记录 List resultList = new ArrayList<>(); for(TimePeriodExamRoomEntity item : toBeSaveList) { resultList.add(item.getTimePeriodId()); } //更新的记录,值有变化 for (TimePeriodExamRoomEntity item : toBeUpdateList) { TimePeriodExamSiteBean timePeriod = timePeriodMap.get(item.getId()); if(timePeriod != null && !timePeriod.getEnable().equals(item.getEnable())) { resultList.add(item.getTimePeriodId()); } } // 判断考场+时段是否在库中已经存在 if (CollectionUtils.isNotEmpty(toBeSaveList)) { // 提取所有要检查的examRoomId和timePeriodId组合 List examRoomIds = toBeSaveList.stream() .map(TimePeriodExamRoomEntity::getExamRoomId) .distinct() .collect(Collectors.toList()); List timePeriodIds = toBeSaveList.stream() .map(TimePeriodExamRoomEntity::getTimePeriodId) .distinct() .collect(Collectors.toList()); // 批量查询已存在的记录 List existingRecords = getBaseMapper().listByExamRoomIdsAndTimePeriodIds(examRoomIds, timePeriodIds); // 构建已存在的记录集合,用于快速查找 Set existingKeySet = existingRecords.stream() .map(e -> e.getExamRoomId() + "-" + e.getTimePeriodId()) .collect(Collectors.toSet()); // 检查是否有重复的记录 for (TimePeriodExamRoomEntity item : toBeSaveList) { String key = item.getExamRoomId() + "-" + item.getTimePeriodId(); if (existingKeySet.contains(key)) { log.error("[考场排班设置]保存失败,该时间段已存在: examRoomId={}, timePeriodId={}", item.getExamRoomId(), item.getTimePeriodId()); throw new StatusException("保存失败,该时间段已存在"); } } } canEdit(resultList, curApplyTask); saveOrUpdateBatch(timePeriodExamRoomEntityList); //新增的记录,刷新缓存,只考虑禁用的情况 refreshDisableTimePeriodCache(toBeSaveList, examRoom); //更新的记录,刷新缓存 refreshUpdatedTimePeriodCache(toBeUpdateList, timePeriodMap, examRoom); } private TimePeriodExamRoomEntity createTimePeriodExamRoomEntity(TimePeriodExamSiteReq item, Long examRoomId, Long userId) { TimePeriodExamRoomEntity entity = new TimePeriodExamRoomEntity(); BeanUtils.copyProperties(item, entity); entity.setExamRoomId(examRoomId); entity.setOperateId(userId); return entity; } private void refreshDisableTimePeriodCache(List disableList, ExamRoomEntity examRoom) { disableList.stream() .filter(item -> !item.getEnable()) .forEach(item -> { String examSiteTimePeriodLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_TIME_PERIOD, examRoom.getExamSiteId(), item.getTimePeriodId()); RLock examSiteTimePeriodLock = (RLock) concurrentService.getLock(examSiteTimePeriodLockKey); try { if (!examSiteTimePeriodLock.tryLock()) { log.warn("[考场排班设置] 获取锁失败,不能同时修改考场排班设置, lockKey:{}", examSiteTimePeriodLock); throw new StatusException("考场排班保存失败,请稍后再试"); } else { // 更新容量缓存,禁用需要减考场容量 applyTaskCacheService.refreshApplyAvailableCountCache( examRoom.getExamSiteId(), item.getTimePeriodId(), // examSite.getCapacity(), // examSite.getCapacity()- examRoom.getCapacity() examRoom.getCapacity(), 0 ); } } catch (Exception e) { log.error("[考场排班设置] 考场排班失败, msg:{}", e.getMessage()); throw new StatusException("考场排班保存失败,请稍后再试"); } finally { try { if (examSiteTimePeriodLock.isLocked() && examSiteTimePeriodLock.isHeldByCurrentThread()) { examSiteTimePeriodLock.unlock(); log.info("[考场排班设置] 解锁成功,lockKey:{}", examSiteTimePeriodLock); } } catch (Exception e) { log.warn(e.getMessage()); } } }); } private void refreshUpdatedTimePeriodCache(List updateList, Map timePeriodMap, ExamRoomEntity examRoom) { updateList.forEach(item -> { TimePeriodExamSiteBean timePeriod = timePeriodMap.get(item.getId()); String examSiteTimePeriodLockKey = String.format(CacheConstants.LOCK_EXAM_SITE_TIME_PERIOD, examRoom.getExamSiteId(), item.getTimePeriodId()); RLock examSiteTimePeriodLock = (RLock) concurrentService.getLock(examSiteTimePeriodLockKey); try { if (!examSiteTimePeriodLock.tryLock()) { log.warn("[考场排班设置] 获取锁失败,不能同时修改考场排班设置, lockKey:{}", examSiteTimePeriodLock); throw new StatusException("考场排班保存失败,请稍后再试"); } else { //由启用变为禁用,缓存容量=-考场的容量 if (timePeriod != null && Boolean.TRUE.equals(timePeriod.getEnable()) && !item.getEnable()) { // 更新容量缓存 applyTaskCacheService.refreshApplyAvailableCountCache( examRoom.getExamSiteId(), timePeriod.getTimePeriodId(), // examSite.getCapacity(), // examSite.getCapacity() - examRoom.getCapacity() examRoom.getCapacity(), 0 ); } //由禁用变为启用,缓存容量=+考场的容量 if (timePeriod != null && Boolean.FALSE.equals(timePeriod.getEnable()) && item.getEnable()) { //更新容量缓存 applyTaskCacheService.refreshApplyAvailableCountCache( examRoom.getExamSiteId(), timePeriod.getTimePeriodId(), // examSite.getCapacity(), // examSite.getCapacity() + examRoom.getCapacity() 0, examRoom.getCapacity() ); } } } catch (Exception e) { log.error("[考场排班设置] 考场排班失败, msg:{}", e.getMessage()); throw new StatusException("考场排班保存失败,请稍后再试"); } finally { try { if (examSiteTimePeriodLock.isLocked() && examSiteTimePeriodLock.isHeldByCurrentThread()) { examSiteTimePeriodLock.unlock(); log.info("[考场排班设置] 解锁成功,lockKey:{}", examSiteTimePeriodLock); } } catch (Exception e) { log.warn(e.getMessage()); } } }); } }