Browse Source

自动预约考试-修改为异步执行

haogh 1 năm trước cách đây
mục cha
commit
248893c629

+ 1 - 1
src/main/java/com/qmth/exam/reserve/controller/admin/StudentApplyController.java

@@ -112,7 +112,7 @@ public class StudentApplyController extends BaseController {
         }
         List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
         try {
-            failRecords = studentApplyService.importPreExam(user.getId(), teachingId, level, file.getInputStream());
+            failRecords = studentApplyService.importPreExam(user, teachingId, level, file.getInputStream());
         } catch (IOException e) {
             throw new StatusException("文件读取出错", e);
         }

+ 1 - 1
src/main/java/com/qmth/exam/reserve/controller/admin/StudentImportTaskController.java

@@ -79,7 +79,7 @@ public class StudentImportTaskController extends BaseController {
 
     @ApiOperation(value = "导出异常信息")
     @GetMapping(value = "/export/message")
-    public void exportExcepionMessage(@ApiParam("导入的任务ID") @RequestParam Long id, HttpServletResponse response) {
+    public void exportExceptionMessage(@ApiParam("导入的任务ID") @RequestParam Long id, HttpServletResponse response) {
         try {
             String fileName = URLEncoder.encode("异常信息", "UTF-8");
             response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");

+ 2 - 1
src/main/java/com/qmth/exam/reserve/service/StudentApplyService.java

@@ -12,6 +12,7 @@ import com.qmth.exam.reserve.bean.stdapply.SignInVO;
 import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
 import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
 import com.qmth.exam.reserve.entity.StudentApplyEntity;
+import com.qmth.exam.reserve.entity.UserEntity;
 
 public interface StudentApplyService extends IService<StudentApplyEntity> {
 
@@ -19,7 +20,7 @@ public interface StudentApplyService extends IService<StudentApplyEntity> {
 
     void cancel(LoginUser user, Long id);
 
-    List<Map<String, Object>> importPreExam(Long userId, Long teachingId, Integer level, InputStream inputStream);
+    List<Map<String, Object>> importPreExam(LoginUser user, Long teachingId, Integer level, InputStream inputStream);
 
     void autoAssign(Long taskId, Long userId);
 

+ 6 - 0
src/main/java/com/qmth/exam/reserve/service/StudentAutoAssignService.java

@@ -0,0 +1,6 @@
+package com.qmth.exam.reserve.service;
+
+public interface StudentAutoAssignService {
+
+    void autoAssign(Long taskId, Long userId);
+}

+ 88 - 213
src/main/java/com/qmth/exam/reserve/service/impl/StudentApplyServiceImpl.java

@@ -16,6 +16,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO;
+import com.qmth.exam.reserve.service.*;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateUtils;
 import org.redisson.api.RLock;
@@ -59,19 +61,12 @@ import com.qmth.exam.reserve.entity.StudentEntity;
 import com.qmth.exam.reserve.entity.TimePeriodEntity;
 import com.qmth.exam.reserve.enums.CategoryLevel;
 import com.qmth.exam.reserve.enums.EventType;
-import com.qmth.exam.reserve.service.ApplyTaskService;
-import com.qmth.exam.reserve.service.CategoryService;
-import com.qmth.exam.reserve.service.ExamRoomService;
-import com.qmth.exam.reserve.service.ExamSiteService;
-import com.qmth.exam.reserve.service.MaterialGenerateService;
-import com.qmth.exam.reserve.service.OperateLogService;
-import com.qmth.exam.reserve.service.StudentApplyService;
-import com.qmth.exam.reserve.service.StudentService;
-import com.qmth.exam.reserve.service.TimePeriodService;
 import com.qmth.exam.reserve.util.DateUtil;
 import com.qmth.exam.reserve.util.JsonHelper;
 import com.qmth.exam.reserve.util.PageUtil;
 
+import static org.apache.commons.lang3.time.DateUtils.isSameDay;
+
 @Service
 public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity>
         implements StudentApplyService {
@@ -111,6 +106,9 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     @Autowired
     private ApplyTaskCacheService cacheService;
 
+    @Autowired
+    private StudentAutoAssignService studentAutoAssignService;
+
     @Override
     public PageResult<StudentApplyVO> page(StudentApplyReq req) {
         if (req.getTaskId() == null) {
@@ -192,8 +190,9 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
 
     @Transactional
     @Override
-    public List<Map<String, Object>> importPreExam(Long userId, Long teachingId, Integer level,
+    public List<Map<String, Object>> importPreExam(LoginUser user, Long teachingId, Integer level,
             InputStream inputStream) {
+        checkOpenTime(user.getOrgId());
         List<DataMap> lineList;
         ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
         try {
@@ -395,7 +394,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         for (int i = 0; i < applyList.size(); i++) {
             StudentImportVO vo = applyList.get(i);
             try {
-                saveStdApply(i, vo, userId, failRecords);
+                saveStdApply(i, vo, user.getId(), failRecords);
             } catch (StatusException e) {
                 failRecords.add(newError(i + 1, " 系统异常"));
                 log.error("导入异常", e);
@@ -411,6 +410,16 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
 
     }
 
+    private void checkOpenTime(Long orgId) {
+        CurrentApplyTaskVO task = cacheService.currentApplyTask(orgId);
+        Date startTime = new Date(task.getSelfApplyStartTime());
+        Date endTime = new Date(task.getOpenApplyEndTime());
+        Date now = new Date();
+        if(now.before(startTime) || now.after(endTime)) {
+            throw new StatusException("未到开放时间!");
+        }
+    }
+
     private Map<Long, Long> getExamSiteCategoryCache() {
         Map<Long, Long> cache = new HashMap<>();
         LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
@@ -644,145 +653,61 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     @Override
     public void autoAssign(Long taskId, Long userId) {
         checkAfterOpenTime();
-        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
-        try {
-            if (!lock.tryLock()) {
-                log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
-                throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
-            }
-
-            log.warn("获取锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
-
-            // 1、未完成预约的考生
-            List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
-            Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream()
-                    .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
-            // 2、考位是否充足
-            List<TimePeriodEntity> timeList = listTimePeriod(taskId);
-            timeList = listNoCancelExamTimePeriod(timeList, taskId);
-            checkTeachingCapacity(noFinishApplyMap, timeList, taskId);
-            // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
-            for (Long key : noFinishApplyMap.keySet()) {
-                List<ExamSiteEntity> siteList = listExamSite(key, null);
-                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
-                for (ExamSiteEntity site : siteList) {
-                    for (TimePeriodEntity time : timeList) {
-                        // 该时段已预约的考生
-                        Integer haveApplyNum = cacheService.getApplyFinishCount(site.getId(), time.getId());
-                        if (haveApplyNum == 0) {
-                            haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
-                        }
-                        // 剩余的考位
-                        Integer remainNum = site.getCapacity() - haveApplyNum;
-
-                        assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
-                    }
-                }
-                // 4、判断是否还有剩余考生未完成预约,提醒考位不够
-                if (!teachingStudentList.isEmpty())
-                    throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
-            }
-        } catch (Exception e) {
-            log.error(e.getMessage());
-            throw new StatusException(e.getMessage());
-        } finally {
-            try {
-                // 解锁前检查当前线程是否持有该锁
-                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
-                    lock.unlock();
-                    log.info("解锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
-                }
-            } catch (Exception e) {
-                log.warn(e.getMessage());
-            }
-        }
-
-    }
-
-    private List<TimePeriodEntity> listNoCancelExamTimePeriod(List<TimePeriodEntity> timeList, Long taskId) {
-        ApplyTaskEntity task = applyTaskService.getById(taskId);
-        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, task.getAllowApplyCancelDays());
-        Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59");
-        return timeList.stream().filter(time -> time.getStartTime() > longOtherDay).collect(Collectors.toList());
-    }
-
-    private List<StudentApplyEntity> assignStudentApply(Long userId, Long siteId, Long timeId,
-            List<StudentEntity> teachingStudentList, Integer remainNum) {
-        List<StudentApplyEntity> insertApplyList = new ArrayList<>();
-        int num = 0;
-        String studentApplyLockKey = null;
-        RLock studentApplyLock = null;
-        for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
-            StudentEntity student = iterator.next();
-            if (num >= remainNum)
-                break;
-            studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
-            studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
-            try {
-                if (!studentApplyLock.tryLock()) {
-                    log.warn("获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
-                    iterator.remove();
-                } else {
-                    log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
-
-                    List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId(), Boolean.FALSE);
-                    int toApplyNum = student.getApplyNumber() - studentApplyList.size();
-                    if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
-                        StudentApplyEntity studentApply = new StudentApplyEntity();
-                        studentApply.setStudentId(student.getId());
-                        studentApply.setExamSiteId(siteId);
-                        studentApply.setCancel(Boolean.FALSE);
-                        studentApply.setTimePeriodId(timeId);
-                        studentApply.setOperateId(userId);
-
-                        StudentApplyEntity existStudentApply = findStudentApply(studentApply);
-                        if (existStudentApply != null) {
-                            existStudentApply.setCancel(Boolean.FALSE);
-                            baseMapper.updateById(existStudentApply);
-                        } else {
-                            baseMapper.insert(studentApply);
-                        }
-                        insertApplyList.add(studentApply);
+        //异步执行
+        studentAutoAssignService.autoAssign(taskId, userId);
+//        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
+//        try {
+//            if (!lock.tryLock()) {
+//                log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+//                throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
+//            }
+//
+//            log.warn("获取锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+//
+//            // 1、未完成预约的考生
+//            List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
+//            Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream()
+//                    .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
+//            // 2、考位是否充足
+//            List<TimePeriodEntity> timeList = listTimePeriod(taskId);
+//            timeList = listNoCancelExamTimePeriod(timeList, taskId);
+//            checkTeachingCapacity(noFinishApplyMap, timeList, taskId);
+//            // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
+//            for (Long key : noFinishApplyMap.keySet()) {
+//                List<ExamSiteEntity> siteList = listExamSite(key, null);
+//                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
+//                for (ExamSiteEntity site : siteList) {
+//                    for (TimePeriodEntity time : timeList) {
+//                        // 该时段已预约的考生
+//                        Integer haveApplyNum = cacheService.getApplyFinishCount(site.getId(), time.getId());
+//                        if (haveApplyNum == 0) {
+//                            haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
+//                        }
+//                        // 剩余的考位
+//                        Integer remainNum = site.getCapacity() - haveApplyNum;
+//
+//                        assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
+//                    }
+//                }
+//                // 4、判断是否还有剩余考生未完成预约,提醒考位不够
+//                if (!teachingStudentList.isEmpty())
+//                    throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
+//            }
+//        } catch (Exception e) {
+//            log.error(e.getMessage());
+//            throw new StatusException(e.getMessage());
+//        } finally {
+//            try {
+//                // 解锁前检查当前线程是否持有该锁
+//                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+//                    lock.unlock();
+//                    log.info("解锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+//                }
+//            } catch (Exception e) {
+//                log.warn(e.getMessage());
+//            }
+//        }
 
-                        num++;
-                        if (student.getApplyNumber() - (studentApplyList.size() + 1) == 0) {
-                            iterator.remove();
-                        }
-
-                        ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
-                        bean.setStudentId(studentApply.getStudentId());
-                        bean.setExamSiteId(studentApply.getExamSiteId());
-                        bean.setTimePeriodId(studentApply.getTimePeriodId());
-                        bean.setCancel(Boolean.FALSE);
-                        bean.setOperateId(userId);
-                        bean.setOperateTime(System.currentTimeMillis());
-
-                        // 先推送至预约队列
-                        cacheService.pushStudentApplyRecordQueue(bean);
-                        cacheService.saveStudentApplyRecord(bean);
-                        cacheService.increaseApplyFinishCount(studentApply.getExamSiteId(),
-                                studentApply.getTimePeriodId());
-                    }
-                }
-            } catch (Exception e) {
-                log.error("自动安排预约失败,错误信息:{}", e.getMessage());
-                throw new StatusException("自动安排预约失败,请稍后再试!");
-            } finally {
-                try {
-                    // 解锁前检查当前线程是否持有该锁
-                    if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
-                        studentApplyLock.unlock();
-                        log.info("解锁成功!lockKey:{}", studentApplyLockKey);
-                    }
-                } catch (Exception e) {
-                    log.warn(e.getMessage());
-                }
-            }
-
-        }
-        return insertApplyList;
     }
 
     private StudentApplyEntity findStudentApply(StudentApplyEntity studentApply) {
@@ -793,24 +718,6 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         return baseMapper.selectOne(lm);
     }
 
-    private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
-        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
-        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
-        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
-        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
-        StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
-        return studentApply != null;
-    }
-
-    private Integer getHaveApplyNum(Long siteId, Long timeId) {
-        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
-        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
-        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
-        return baseMapper.selectCount(wrapper);
-    }
-
     private void checkAfterOpenTime() {
         ApplyTaskEntity task = getApplyTask();
         Date selfEndTime = new Date(task.getSelfApplyEndTime());
@@ -820,45 +727,6 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         }
     }
 
-    private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList,
-            Long taskId) {
-        for (Long key : map.keySet()) {
-            List<ExamSiteEntity> siteList = listExamSite(key, null);
-            // 总考位数量
-            Integer total = siteList.stream().collect(Collectors.summingInt(ExamSiteEntity::getCapacity))
-                    * timeList.size();
-            // 已经预约的数量
-            Integer haveApplyNum = 0;
-            for (ExamSiteEntity site : siteList) {
-                haveApplyNum += cacheService.getApplyTotalCount(site.getId());
-            }
-            if (haveApplyNum == 0) {
-                haveApplyNum = getBaseMapper().getHaveApplyCount(
-                        siteList.stream().map(site -> site.getId()).collect(Collectors.toList()), Boolean.FALSE);
-            }
-            // 未预约的数量
-            Integer noApplyNum = getNoApplyNum(map.get(key));
-            if (noApplyNum > total - haveApplyNum) {
-                CategoryEntity category = categoryService.getById(key);
-                throw new StatusException("【" + category.getName() + "】教学点考位不足!剩余的考位数量:【" + (total - haveApplyNum)
-                        + "】,实际需要的考位数量:【" + noApplyNum + "】");
-            }
-        }
-    }
-
-    private Integer getNoApplyNum(List<StudentEntity> list) {
-        int noApplyNum = 0;
-        for (StudentEntity student : list) {
-            if (student.getApplyNumber() == 1) {
-                noApplyNum++;
-            } else if (student.getApplyNumber() > 1) {
-                // listStudentApply(student.getId(), Boolean.FALSE).size()
-                int haveApplyNum = cacheService.getStudentApplyFinishCount(student.getId());
-                noApplyNum = noApplyNum + (student.getApplyNumber() - haveApplyNum);
-            }
-        }
-        return noApplyNum;
-    }
 
     private List<StudentApplyEntity> listStudentApply(Long stdId, Boolean cancel) {
         LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
@@ -990,7 +858,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         Date day = new Date(startTime);
         List<TimePeriodEntity> resultList = new ArrayList<>();
         for (TimePeriodEntity time : timeList) {
-            if (DateUtils.isSameDay(day, new Date(time.getStartTime())))
+            if (isSameDay(day, new Date(time.getStartTime())))
                 resultList.add(time);
         }
         return resultList.stream().sorted(Comparator.comparing(TimePeriodEntity::getStartTime))
@@ -1014,7 +882,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         Date noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
         List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
         for (TimePeriodEntity time : list) {
-            if (DateUtils.isSameDay(noCancelDate, new Date(time.getStartTime())))
+            if (isSameDay(noCancelDate, new Date(time.getStartTime())))
                 noCancelTimePeroidList.add(time);
         }
         return noCancelTimePeroidList;
@@ -1114,13 +982,10 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
             log.warn("未配置考试时段");
             return signInList;
         }
-        Long startTime = timePeriodList.get(0).getStartTime();
-        Long endTime = timePeriodList.get(timePeriodList.size() - 1).getEndTime();
-
         String todayStr = DateUtil.formatShortSplitDateString(new Date());
         Long longToday = DateUtil.getLongTimeByDate(todayStr + " 00:00:00");
         Date today = new Date(longToday);
-        if (longToday >= startTime && longToday <= endTime) {
+        if (isInTimePeriod(today,timePeriodList)) {
             SignInVO vo = new SignInVO();
             vo.setExamDate(longToday);
             signInList.add(vo);
@@ -1129,7 +994,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         for (int i = 1; i <= task.getAllowApplyCancelDays(); i++) {
             Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, i);
             Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 00:00:00");
-            if (longOtherDay >= startTime && longOtherDay <= endTime) {
+            if (isInTimePeriod(otherDay, timePeriodList)) {
                 SignInVO vo = new SignInVO();
                 vo.setExamDate(longOtherDay);
                 signInList.add(vo);
@@ -1139,4 +1004,14 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         return signInList;
     }
 
+    private boolean isInTimePeriod(Date date, List<TimePeriodEntity> timePeriodList) {
+        for(TimePeriodEntity timePeriod : timePeriodList) {
+            Date day = new Date(timePeriod.getStartTime());
+            if(DateUtils.isSameDay(day, date)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }

+ 287 - 0
src/main/java/com/qmth/exam/reserve/service/impl/StudentAutoAssignServiceImpl.java

@@ -0,0 +1,287 @@
+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.apply.ApplyRecordCacheBean;
+import com.qmth.exam.reserve.cache.CacheConstants;
+import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
+import com.qmth.exam.reserve.dao.StudentApplyDao;
+import com.qmth.exam.reserve.entity.*;
+import com.qmth.exam.reserve.entity.base.BaseEntity;
+import com.qmth.exam.reserve.service.*;
+import com.qmth.exam.reserve.util.DateUtil;
+import org.redisson.api.RLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity> implements StudentAutoAssignService {
+
+    private final Logger log = LoggerFactory.getLogger(StudentAutoAssignServiceImpl.class);
+
+    @Autowired
+    private ConcurrentService concurrentService;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Autowired
+    private TimePeriodService timePeriodService;
+
+    @Autowired
+    private ApplyTaskCacheService cacheService;
+
+    @Autowired
+    private CategoryService categoryService;
+
+    @Autowired
+    private ApplyTaskService applyTaskService;
+
+    @Autowired
+    private ExamSiteService examSiteService;
+
+    @Async
+    @Override
+    public void autoAssign(Long taskId, Long userId) {
+        log.warn("开始自动预约考试");
+        long start = System.currentTimeMillis();
+        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
+        try {
+            if (!lock.tryLock()) {
+                log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+                throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
+            }
+
+            log.warn("获取锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+
+            // 1、未完成预约的考生
+            List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
+            Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream().collect(Collectors.groupingBy(StudentEntity::getCategoryId));
+            // 2、考位是否充足
+            List<TimePeriodEntity> timeList = listTimePeriod(taskId);
+            timeList = listNoCancelExamTimePeriod(timeList, taskId);
+            if (timeList == null || timeList.isEmpty()) {
+                throw new StatusException("可以取消预约的考试时段为0");
+            }
+            checkTeachingCapacity(noFinishApplyMap, timeList, taskId);
+            // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
+            for (Long key : noFinishApplyMap.keySet()) {
+                List<ExamSiteEntity> siteList = listExamSite(key);
+                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
+                for (ExamSiteEntity site : siteList) {
+                    for (TimePeriodEntity time : timeList) {
+                        // 该时段已预约的考生
+                        Integer haveApplyNum = cacheService.getApplyFinishCount(site.getId(), time.getId());
+                        if (haveApplyNum == 0) {
+                            haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
+                        }
+                        // 剩余的考位
+                        Integer remainNum = site.getCapacity() - haveApplyNum;
+
+                        assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
+                    }
+                }
+                // 4、判断是否还有剩余考生未完成预约,提醒考位不够
+                if (!teachingStudentList.isEmpty())
+                    throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            throw new StatusException(e.getMessage());
+        } finally {
+            try {
+                // 解锁前检查当前线程是否持有该锁
+                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
+                    lock.unlock();
+                    log.info("解锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
+            }
+        }
+
+        long end = System.currentTimeMillis();
+        log.warn("完成自动预约,耗时:{}s", (end - start) / 1000);
+    }
+
+    private List<TimePeriodEntity> listTimePeriod(Long taskId) {
+        LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TimePeriodEntity::getApplyTaskId, taskId);
+        wrapper.orderByAsc(TimePeriodEntity::getStartTime);
+        List<TimePeriodEntity> timeList = timePeriodService.list(wrapper);
+        if (timeList.isEmpty()) {
+            throw new StatusException("考试时段未设置");
+        }
+        return timeList;
+    }
+
+    private List<TimePeriodEntity> listNoCancelExamTimePeriod(List<TimePeriodEntity> timeList, Long taskId) {
+        ApplyTaskEntity task = applyTaskService.getById(taskId);
+        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, task.getAllowApplyCancelDays());
+        Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59");
+        return timeList.stream().filter(time -> time.getStartTime() > longOtherDay).collect(Collectors.toList());
+    }
+
+    private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList, Long taskId) {
+        for (Long key : map.keySet()) {
+            List<ExamSiteEntity> siteList = listExamSite(key);
+            // 总考位数量
+            Integer total = siteList.stream().mapToInt(ExamSiteEntity::getCapacity).sum() * timeList.size();
+            // 已经预约的数量
+            Integer haveApplyNum = 0;
+            for (ExamSiteEntity site : siteList) {
+                haveApplyNum += cacheService.getApplyTotalCount(site.getId());
+            }
+            if (haveApplyNum == 0) {
+                haveApplyNum = baseMapper.getHaveApplyCount(siteList.stream().map(BaseEntity::getId).collect(Collectors.toList()), Boolean.FALSE);
+            }
+            // 未预约的数量
+            Integer noApplyNum = getNoApplyNum(map.get(key));
+            if (noApplyNum > total - haveApplyNum) {
+                CategoryEntity category = categoryService.getById(key);
+                throw new StatusException("【" + category.getName() + "】教学点考位不足!剩余的考位数量:【" + (total - haveApplyNum) + "】,实际需要的考位数量:【" + noApplyNum + "】");
+            }
+        }
+    }
+
+    private List<ExamSiteEntity> listExamSite(Long categoryId) {
+        LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ExamSiteEntity::getCategoryId, categoryId);
+        wrapper.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
+        return examSiteService.list(wrapper);
+    }
+
+    private Integer getHaveApplyNum(Long siteId, Long timeId) {
+        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
+        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
+        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        return baseMapper.selectCount(wrapper);
+    }
+
+    private List<StudentApplyEntity> assignStudentApply(Long userId, Long siteId, Long timeId,
+                                                        List<StudentEntity> teachingStudentList, Integer remainNum) {
+        List<StudentApplyEntity> insertApplyList = new ArrayList<>();
+        int num = 0;
+        String studentApplyLockKey = null;
+        RLock studentApplyLock = null;
+        for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext(); ) {
+            StudentEntity student = iterator.next();
+            if (num >= remainNum)
+                break;
+            studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
+            studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
+            try {
+                if (!studentApplyLock.tryLock()) {
+                    log.warn("获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
+                    iterator.remove();
+                } else {
+                    log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
+
+                    List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId());
+                    int toApplyNum = student.getApplyNumber() - studentApplyList.size();
+                    if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
+                        StudentApplyEntity studentApply = new StudentApplyEntity();
+                        studentApply.setStudentId(student.getId());
+                        studentApply.setExamSiteId(siteId);
+                        studentApply.setCancel(Boolean.FALSE);
+                        studentApply.setTimePeriodId(timeId);
+                        studentApply.setOperateId(userId);
+
+                        StudentApplyEntity existStudentApply = findStudentApply(studentApply);
+                        if (existStudentApply != null) {
+                            existStudentApply.setCancel(Boolean.FALSE);
+                            baseMapper.updateById(existStudentApply);
+                        } else {
+                            baseMapper.insert(studentApply);
+                        }
+                        insertApplyList.add(studentApply);
+
+                        num++;
+                        if (student.getApplyNumber() - (studentApplyList.size() + 1) == 0) {
+                            iterator.remove();
+                        }
+
+                        ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
+                        bean.setStudentId(studentApply.getStudentId());
+                        bean.setExamSiteId(studentApply.getExamSiteId());
+                        bean.setTimePeriodId(studentApply.getTimePeriodId());
+                        bean.setCancel(Boolean.FALSE);
+                        bean.setOperateId(userId);
+                        bean.setOperateTime(System.currentTimeMillis());
+
+                        // 先推送至预约队列
+                        cacheService.pushStudentApplyRecordQueue(bean);
+                        cacheService.saveStudentApplyRecord(bean);
+                        cacheService.increaseApplyFinishCount(studentApply.getExamSiteId(),
+                                studentApply.getTimePeriodId());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("自动安排预约失败,错误信息:{}", e.getMessage());
+                throw new StatusException("自动安排预约失败,请稍后再试!");
+            } finally {
+                try {
+                    // 解锁前检查当前线程是否持有该锁
+                    if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
+                        studentApplyLock.unlock();
+                        log.info("解锁成功!lockKey:{}", studentApplyLockKey);
+                    }
+                } catch (Exception e) {
+                    log.warn(e.getMessage());
+                }
+            }
+
+        }
+        return insertApplyList;
+    }
+
+    private Integer getNoApplyNum(List<StudentEntity> list) {
+        int noApplyNum = 0;
+        for (StudentEntity student : list) {
+            if (student.getApplyNumber() == 1) {
+                noApplyNum++;
+            } else if (student.getApplyNumber() > 1) {
+                // listStudentApply(student.getId(), Boolean.FALSE).size()
+                int haveApplyNum = cacheService.getStudentApplyFinishCount(student.getId());
+                noApplyNum = noApplyNum + (student.getApplyNumber() - haveApplyNum);
+            }
+        }
+        return noApplyNum;
+    }
+
+    private List<StudentApplyEntity> listStudentApply(Long stdId) {
+        LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
+        lm.eq(StudentApplyEntity::getStudentId, stdId);
+        lm.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        return this.baseMapper.selectList(lm);
+    }
+
+    private StudentApplyEntity findStudentApply(StudentApplyEntity studentApply) {
+        LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
+        lm.eq(StudentApplyEntity::getExamSiteId, studentApply.getExamSiteId());
+        lm.eq(StudentApplyEntity::getTimePeriodId, studentApply.getTimePeriodId());
+        lm.eq(StudentApplyEntity::getStudentId, studentApply.getStudentId());
+        return baseMapper.selectOne(lm);
+    }
+
+    private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
+        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
+        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
+        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
+        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
+        return studentApply != null;
+    }
+}

+ 2 - 1
src/main/java/com/qmth/exam/reserve/service/impl/StudentImportAsyncServiceImpl.java

@@ -149,7 +149,7 @@ public class StudentImportAsyncServiceImpl implements StudentImportAsyncService
 
     private void updateStudentImportTask(StudentImportTaskEntity task, String status,
             List<Map<String, Object>> failRecords) {
-        task.setMessage(failRecords.size() == 0 ? null : failRecords.toString());
+        task.setMessage(failRecords.isEmpty() ? null : failRecords.toString());
         task.setStatus(status);
         importTaskService.saveOrUpdate(task);
     }
@@ -169,6 +169,7 @@ public class StudentImportAsyncServiceImpl implements StudentImportAsyncService
             cacheService.clearStudentApplyNumberCache(existStudent.getId());
         } else {
             studentEntity.setApplyFinished(Boolean.FALSE);
+            studentEntity.setPhotoPath(studentEntity.getIdentityNumber()+".jpg");
             studentService.save(studentEntity);
         }
     }

+ 1 - 1
src/main/resources/mapper/StudentApplyMapper.xml

@@ -48,7 +48,7 @@
     </select>
 
     <select id="getHaveApplyCount" resultType="int">
-        select count(sa.id) from t_student_apply sa, t_exam_site s where sa.exam_site_id=s.id and sa.cancel=1 and
+        select count(sa.id) from t_student_apply sa, t_exam_site s where sa.exam_site_id=s.id and sa.cancel=0 and
         sa.exam_site_id in
         <foreach collection="examSiteIds" item="item" index="index" separator="," open="(" close=")">
             #{item}