haogh 8 ヶ月 前
コミット
56b5f0773c

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

@@ -126,7 +126,7 @@ public class StudentApplyController extends BaseController {
             throw new StatusException("没有权限");
         }
         studentApplyService.autoAssign(taskId, user.getId());
-        return "后台正在执行中,请耐心等待!";
+        return Constants.ASYNC_TIPS;
     }
 
     @ApiOperation(value = "打印签到表")

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

@@ -6,5 +6,5 @@ import java.util.List;
 
 public interface StudentAutoAssignService {
 
-    void autoAssign(Long taskId, Long userId, List<TimePeriodEntity>timeList);
+    String autoAssign(Long taskId, Long userId);
 }

+ 151 - 24
src/main/java/com/qmth/exam/reserve/service/impl/StudentApplyServiceImpl.java

@@ -31,6 +31,7 @@ import com.qmth.exam.reserve.enums.CategoryLevel;
 import com.qmth.exam.reserve.enums.EventType;
 import com.qmth.exam.reserve.enums.Role;
 import com.qmth.exam.reserve.service.*;
+import com.qmth.exam.reserve.template.execute.AutoAssignStudentService;
 import com.qmth.exam.reserve.template.execute.StudentApplyDetailExportService;
 import com.qmth.exam.reserve.template.execute.StudentApplyNoFinishExportService;
 import com.qmth.exam.reserve.util.DateUtil;
@@ -120,6 +121,9 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     @Autowired
     private StudentApplyDetailExportService studentApplyDetailExportService;
 
+    @Autowired
+    private AutoAssignStudentService autoAssignStudentService;
+
     @Override
     public PageResult<StudentApplyVO> page(StudentApplyReq req) {
         if (req.getTaskId() == null) {
@@ -765,34 +769,51 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     }
 
     @Override
-    public void autoAssign(Long taskId, Long userId) {
-        checkAfterOpenTime();
-        //未完成预约教学点
-        List<CategoryVO> teachingList = studentService.listNoFinishCategory(taskId, Boolean.FALSE);
+    public void autoAssign(Long taskId, Long operateId) {
+        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
+        if (lock.isLocked()) {
+            log.warn("[autoAssign] 获取锁失败, taskId:{}, lockKey:{}", taskId, CacheConstants.LOCK_AUTO_APPLY);
+            throw new StatusException("自动分配执行未结束,请不要重复执行");
+        }
+
+        // 自动分配须在第一阶段之后和第三阶段开始之前
+        CurrentApplyTaskVO curApplyTask = applyTaskService.currentApplyTask(null);
+        Date selfEndTime = new Date(curApplyTask.getSelfApplyEndTime());
+        Date openStartTime = new Date(curApplyTask.getOpenApplyStartTime());
+        if (!DateUtil.isBetwwen(selfEndTime, openStartTime)) {
+            //throw new StatusException("自动分配,时间必须要在第一阶段结束之后,第三阶段开始之前");
+        }
+
+        //是否有可预约的时段
         List<TimePeriodEntity> timePeriodList = listTimePeriod(taskId);
         timePeriodList = listNoCancelTimePeriod(timePeriodList, taskId);
-        if (timePeriodList == null || timePeriodList.isEmpty()) {
-            log.warn("[autoAssign] 当前时间没有可预约的时段, taskId:{}, userId:{}", taskId, userId);
+        if (CollectionUtils.isEmpty(timePeriodList)) {
+            log.warn("[autoAssign] 当前时间没有可预约的时段, taskId:{}", taskId);
             throw new StatusException("当前时间没有可预约的时段");
         }
 
-        //考位是否充足
-        checkTeachingCapacity(taskId, teachingList, timePeriodList);
+        /*//未完成预约教学点
+            List<CategoryVO> teachingList = studentService.listNoFinishCategory(taskId, Boolean.FALSE);
+            //考位是否充足
+            checkTeachingCapacity(taskId, teachingList, timePeriodList);
+            studentAutoAssignService.autoAssign(taskId, userId, timePeriodList);
+        */
 
-        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
-        if (lock.isLocked()) {
-            log.warn("[autoAssign] 获取锁失败, taskId:{}, lockKey:{}", taskId, CacheConstants.LOCK_AUTO_APPLY);
-            throw new StatusException("自动分配执行未结束,请不要重复执行");
+        //写入异步任务
+        Map<String, Object> taskMap = asyncTaskService.saveAsyncTask(AsyncTaskType.AUTO_ASSIGN, operateId);
+        taskMap.computeIfAbsent("taskId", v -> taskId);
+        taskMap.computeIfAbsent("operateId", v -> operateId);
+        try {
+            autoAssignStudentService.assignTask(taskMap);
+        } catch (Exception e) {
+            throw new StatusException(e.getMessage());
         }
-
-        //异步执行
-        studentAutoAssignService.autoAssign(taskId, userId, timePeriodList);
     }
 
     private void checkTeachingCapacity(Long taskId, List<CategoryVO> teachingList, List<TimePeriodEntity> timePeriodList) {
         for (CategoryVO categoryVO : teachingList) {
             List<ExamSiteEntity> examSiteList = listExamSite(categoryVO.getId(), null);
-            if (examSiteList == null || examSiteList.isEmpty()) {
+            if (CollectionUtils.isEmpty(examSiteList)) {
                 log.warn("[autoAssign] teachingId: {} 下无考点", categoryVO.getId());
                 continue;
             }
@@ -844,18 +865,124 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         return timeList;
     }
 
-    private void checkAfterOpenTime() {
-        ApplyTaskEntity task = getApplyTask();
-        Date selfEndTime = new Date(task.getSelfApplyEndTime());
-        Date openStartTime = new Date(task.getOpenApplyStartTime());
-        if (!DateUtil.isBetwwen(selfEndTime, openStartTime)) {
-            throw new StatusException("自动分配,时间必须要在第一阶段结束之后,第三阶段开始之前");
+    @Transactional
+    @Override
+    public void autoLayout(Long teachingId) {
+        ApplyTaskEntity applyTask = getApplyTask();
+        String autoLayoutLockKey = String.format(CacheConstants.LOCK_ARRANGE_EXAM,
+                DateUtil.formatShortDateString(new Date()));
+        RLock autoLayoutLock = (RLock) concurrentService.getLock(autoLayoutLockKey);
+        try {
+            if (!autoLayoutLock.tryLock()) {
+                log.warn("获取锁失败,已有线程在执行排考!lockKey:{}", autoLayoutLock);
+                return;
+            }
+
+            log.warn("获取锁成功!lockKey:{}", autoLayoutLockKey);
+
+            // 1.根据当前日期,查询不能取消的时段
+            List<TimePeriodEntity> timePeriodList = listTimePeriod(applyTask.getId());
+            List<TimePeriodEntity> noCancelTimePeroidList = listNoCancelApplyTimePeriod(timePeriodList,
+                    applyTask.getAllowApplyCancelDays());
+            if (noCancelTimePeroidList.isEmpty()) {
+                log.warn("当前时间不在取消预约范围内");
+                return;
+            }
+            // 2.查询考试日期的待排考的考生
+            List<StudentApplyEntity> toBeLayoutStudentList = this.baseMapper.listTimePeriod(
+                    noCancelTimePeroidList.stream().map(BaseEntity::getId).collect(Collectors.toList()),
+                    Boolean.FALSE);
+            if (toBeLayoutStudentList == null || toBeLayoutStudentList.isEmpty()) {
+                log.warn("没有待排考的考生");
+                return;
+            }
+            // 3.开始排考
+            Map<Long, List<StudentApplyEntity>> toBeLayoutStudentMap = toBeLayoutStudentList.stream()
+                    .collect(Collectors.groupingBy(StudentApplyEntity::getExamSiteId));
+            for (Long examSiteId : toBeLayoutStudentMap.keySet()) {
+                Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap = toBeLayoutStudentMap.get(examSiteId).stream()
+                        .collect(Collectors.groupingBy(StudentApplyEntity::getTimePeriodId));
+                List<ExamRoomEntity> roomList = listExamRoom(examSiteId);
+                if (roomList.isEmpty()) {
+                    log.warn("{}:未设置考场", examSiteId);
+                    return;
+                }
+                ExamSiteEntity examSite = examSiteService.getById(examSiteId);
+                layoutStudentByTimePeriod(applyTask.getId(), examSite, roomList, timeLayoutStudentMap);
+            }
+        } catch (StatusException e) {
+            log.error(e.getMessage());
+        } finally {
+            try {
+                // 解锁前检查当前线程是否持有该锁
+                if (autoLayoutLock.isLocked() && autoLayoutLock.isHeldByCurrentThread()) {
+                    autoLayoutLock.unlock();
+                    log.info("解锁成功!lockKey:{}", autoLayoutLockKey);
+                }
+            } catch (Exception e) {
+                log.warn(e.getMessage());
+            }
         }
     }
 
-    @Override
-    public void autoLayout(Long teachingId) {
+    private void layoutStudentByTimePeriod(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
+            Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap) {
+        for (Long timePeriodId : timeLayoutStudentMap.keySet()) {
+            List<StudentApplyEntity> studentApplyList = timeLayoutStudentMap.get(timePeriodId);
+            layoutStudentToRoom(taskId, examSite, roomList, studentApplyList, timePeriodService.getById(timePeriodId));
+        }
+    }
+
+    private void layoutStudentToRoom(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
+            List<StudentApplyEntity> studentApplyList, TimePeriodEntity timePeriod) {
+        Integer timePeriodOrder = getTimePeriodOrder(taskId, timePeriod);
+        for (ExamRoomEntity room : roomList) {
+            int num = 0;
+            for (Iterator<StudentApplyEntity> iterator = studentApplyList.iterator(); iterator.hasNext(); ) {
+                StudentApplyEntity student = iterator.next();
+                if (num >= room.getCapacity())
+                    break;
+                String seatNumber = StringUtils.leftPad(String.valueOf(++num), 3, '0');
+                student.setExamRoomId(room.getId());
+                student.setSeatNumber(seatNumber);
+                student.setTicketNumber(
+                        generateTicketNumber(timePeriodOrder, examSite.getCode(), room.getCode(), seatNumber));
+                this.baseMapper.updateById(student);
+                iterator.remove();
+            }
+        }
+    }
+
+    private String generateTicketNumber(Integer timePeriodOrder, String examSiteCode, String roomCode,
+            String seatNumber) {
+        return DateUtil.formatShortDateString(new Date()) + timePeriodOrder + examSiteCode + roomCode + seatNumber;
+    }
+
+    private Integer getTimePeriodOrder(Long taskId, TimePeriodEntity timePeriod) {
+        List<TimePeriodEntity> timeList = listTimePeriod(taskId);
+        List<TimePeriodEntity> sameDayTimeList = listSameDayTimePeriod(timeList, timePeriod.getStartTime());
+        for (int i = 0; i < sameDayTimeList.size(); i++) {
+            TimePeriodEntity time = sameDayTimeList.get(i);
+            if (time.getStartTime().equals(timePeriod.getStartTime()))
+                return i + 1;
+        }
+        return 0;
+    }
+
+    private List<TimePeriodEntity> listNoCancelApplyTimePeriod(List<TimePeriodEntity> list,
+            Integer allowApplyCancelDays) {
+        Date noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
+        List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
+        for (TimePeriodEntity time : list) {
+            if (isSameDay(noCancelDate, new Date(time.getStartTime())))
+                noCancelTimePeroidList.add(time);
+        }
+        return noCancelTimePeroidList;
+
+    }
 
+    private Date getNoCancelApplyDate(Integer allowApplyCancelDays) {
+        return DateUtil.addValues(Calendar.DAY_OF_MONTH, allowApplyCancelDays);
     }
 
     @Override

+ 123 - 68
src/main/java/com/qmth/exam/reserve/service/impl/StudentAutoAssignServiceImpl.java

@@ -5,18 +5,24 @@ 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.bean.applytask.CurrentApplyTaskVO;
+import com.qmth.exam.reserve.bean.category.CategoryCacheBean;
+import com.qmth.exam.reserve.bean.timeperiod.TimePeriodExamSiteBean;
 import com.qmth.exam.reserve.cache.CacheConstants;
 import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
+import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
 import com.qmth.exam.reserve.dao.StudentApplyDao;
 import com.qmth.exam.reserve.entity.*;
 import com.qmth.exam.reserve.service.*;
+import com.qmth.exam.reserve.util.DateUtil;
+import org.apache.commons.collections4.CollectionUtils;
 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.text.MessageFormat;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -40,56 +46,70 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
     @Autowired
     private ExamSiteService examSiteService;
 
-    @Async
+    @Autowired
+    private TimePeriodService timePeriodService;
+
+    @Autowired
+    private CategoryCacheService categoryCacheService;
+
+
     @Override
-    public void autoAssign(Long taskId, Long userId, List<TimePeriodEntity> timeList) {
-        log.warn("开始自动预约考试");
-        long start = System.currentTimeMillis();
+    public String autoAssign(Long taskId, Long operateId) {
+        StringJoiner stringJoiner = new StringJoiner("\n");
+        log.warn("[autoAssign] 开始自动预约考试");
         RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
+
         try {
             if (!lock.tryLock()) {
-                log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+                log.warn("[autoAssign] 获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
                 throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
             }
 
-            log.warn("获取锁成功!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
+            log.warn("[autoAssign] 获取锁成功!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);
-                if(siteList.isEmpty()) {
-                    log.warn("教学点{}下没有考点数据,不参与自动分配", key);
+            // 所有预约时段
+            List<TimePeriodExamSiteBean> timeList = timePeriodService.listTimePeriodByTask(taskId);
+
+            stringJoiner.add(MessageFormat.format("未完成预约的考生数:{0} 个", studentList.size()));
+
+            // 考位是否充足
+            /*List<TimePeriodEntity> timeList = listTimePeriod(taskId);
+            timeList = listNoCancelExamTimePeriod(timeList, taskId);
+            checkTeachingCapacity(noFinishApplyMap, timeList, taskId);*/
+            int successNum = 0;
+            // 按照教学点安排考位。规则:不能和已预约的时间上有冲突
+            for (Long teachingId : noFinishApplyMap.keySet()) {
+                List<ExamSiteEntity> siteList = listExamSite(teachingId);
+                if (siteList.isEmpty()) {
+                    log.warn("[autoAssign] 教学点{}下没有考点数据,不参与自动分配", teachingId);
                     continue;
                 }
-                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
+                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(teachingId);
                 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());
-                        }
+                    //考点对应的可用时段
+                    List<TimePeriodExamSiteBean> timePeriodExamSiteList = listAvailableTimePeriod(taskId, site, timeList);
+
+                    for (TimePeriodExamSiteBean time : timePeriodExamSiteList) {
                         // 剩余的考位
-                        int remainNum = site.getCapacity() - haveApplyNum;
-                        if (remainNum < 0) {
-                            log.warn("已预约的考生数超出考点的最大容量,redis中有脏数据。siteId:{} - timeId:{}", site.getId(), time.getId());
-                            throw new StatusException("数据异常");
+                        int remainNum = cacheService.getApplyAvailableCount(site.getId(), time.getTimePeriodId());
+                        if (remainNum > 0) {
+                            int applyNum = assignStudentApply(operateId, site.getId(), time.getTimePeriodId(), teachingStudentList, remainNum);
+                            successNum += applyNum;
                         }
-
-                        assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
                     }
                 }
-                // 4、判断是否还有剩余考生未完成预约,提醒考位不够
-                if (!teachingStudentList.isEmpty())
-                    throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
+                // 判断是否还有剩余考生未完成预约,提醒考位不足
+                if (!teachingStudentList.isEmpty()) {
+                    CategoryCacheBean categoryBean = categoryCacheService.getCategoryById(teachingId);
+                    throw new StatusException("【" + categoryBean.getName() + "】教学点考位不足,还需要【" + teachingStudentList.size() + "】个考位");
+                }
             }
+            stringJoiner.add(MessageFormat.format("自动预约成功的次数为:{0} 次", successNum));
         } catch (Exception e) {
             log.error(e.getMessage());
             throw new StatusException(e.getMessage());
@@ -105,8 +125,28 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
             }
         }
 
-        long end = System.currentTimeMillis();
-        log.warn("完成自动预约,耗时:{}s", (end - start) / 1000);
+        return stringJoiner.toString();
+    }
+
+    private List<TimePeriodExamSiteBean> listAvailableTimePeriod(Long taskId, ExamSiteEntity site, List<TimePeriodExamSiteBean> timeList) {
+        List<TimePeriodExamSiteBean> timePeriodExamSiteList = timePeriodService.listTimePeriodByExamSiteId(taskId, site.getId());
+        if (CollectionUtils.isEmpty(timePeriodExamSiteList)) {
+            timePeriodExamSiteList = timeList;
+        }
+        timePeriodExamSiteList = listNoCancelExamTimePeriod(timePeriodExamSiteList);
+        timePeriodExamSiteList = timePeriodExamSiteList.stream()
+                .filter(TimePeriodExamSiteBean::getEnable)
+                .collect(Collectors.toList());
+        return timePeriodExamSiteList;
+    }
+
+    private List<TimePeriodExamSiteBean> listNoCancelExamTimePeriod(List<TimePeriodExamSiteBean> timePeriodList) {
+        CurrentApplyTaskVO curApplyTask = cacheService.currentApplyTask(null);
+        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");
+        return timePeriodList.stream().filter(time -> time.getStartTime() > longOtherDay).collect(Collectors.toList());
     }
 
 
@@ -117,40 +157,44 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
         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 void assignStudentApply(Long userId, Long siteId, Long timeId, List<StudentEntity> teachingStudentList, Integer remainNum) {
+    private int assignStudentApply(Long operateId, Long siteId, Long timeId, List<StudentEntity> teachingStudentList, int remainNum) {
         int num = 0;
-        String studentApplyLockKey = null;
-        RLock studentApplyLock = null;
+        String studentApplyLockKey;
+        RLock studentApplyLock;
         for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext(); ) {
             StudentEntity student = iterator.next();
-            if (num >= remainNum) break;
+            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);
+                    log.warn("[autoAssign] 获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
                     iterator.remove();
                 } else {
-                    log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
+                    log.warn("[autoAssign] 获取锁成功!lockKey:{}", studentApplyLockKey);
 
-                    //List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId());
+                    // 考生已完成预约的数量
                     int haveApplyCount = cacheService.getStudentApplyFinishCount(student.getId());
+                    // 还需要预约的数量
                     int toApplyNum = student.getApplyNumber() - haveApplyCount;
+
+                    if (toApplyNum <= 0) {
+                        log.warn("[autoAssign] 数据问题, 考生需要预约最大次数:{},已完成预约的数量:{} ", student.getApplyNumber(), haveApplyCount);
+                    }
+
                     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);
+                        studentApply.setOperateId(operateId);
 
                         StudentApplyEntity existStudentApply = findStudentApply(studentApply);
                         if (existStudentApply != null) {
@@ -158,30 +202,31 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
                             baseMapper.updateById(existStudentApply);
                         } else {
                             baseMapper.insert(studentApply);
-                        }
-
-                        num++;
-                        if (student.getApplyNumber() - (haveApplyCount + 1) == 0) {
-                            iterator.remove();
-                        }
+                        }*/
 
                         ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
-                        bean.setStudentId(studentApply.getStudentId());
-                        bean.setExamSiteId(studentApply.getExamSiteId());
-                        bean.setTimePeriodId(studentApply.getTimePeriodId());
+                        bean.setStudentId(student.getId());
+                        bean.setExamSiteId(siteId);
+                        bean.setTimePeriodId(timeId);
                         bean.setCancel(Boolean.FALSE);
-                        bean.setOperateId(userId);
+                        bean.setOperateId(operateId);
                         bean.setOperateTime(System.currentTimeMillis());
 
                         // 先推送至预约队列
                         cacheService.pushStudentApplyRecordQueue(bean);
                         cacheService.saveStudentApplyRecord(bean);
-                        cacheService.increaseApplyFinishCount(studentApply.getExamSiteId(), studentApply.getTimePeriodId());
+                        cacheService.increaseApplyFinishCount(siteId, timeId);
+
+                        num++;
+
+                        //完成所有的预约,移除考生
+                        if (student.getApplyNumber() - (haveApplyCount + 1) == 0) {
+                            iterator.remove();
+                        }
                     }
                 }
             } catch (Exception e) {
-                log.error("自动安排预约失败,错误信息:{}", e.getMessage());
-                throw new StatusException("自动安排预约失败,请稍后再试!");
+                throw new StatusException("自动安排预约失败,失败原因:" + e.getMessage());
             } finally {
                 try {
                     // 解锁前检查当前线程是否持有该锁
@@ -195,6 +240,17 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
             }
 
         }
+        return num;
+    }
+
+    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 List<StudentApplyEntity> listStudentApply(Long stdId) {
@@ -212,13 +268,12 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
         return baseMapper.selectOne(lm);
     }
 
-    private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
+    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::getStudentId, studentId);
         wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
-        StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
-        return studentApply != null;
+        return baseMapper.selectCount(wrapper);
     }
+
 }

+ 20 - 0
src/main/java/com/qmth/exam/reserve/template/assign/AsyncAutoAssignTaskTemplate.java

@@ -0,0 +1,20 @@
+package com.qmth.exam.reserve.template.assign;
+
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * @Description 自动分配模板类
+ */
+public abstract class AsyncAutoAssignTaskTemplate {
+
+    public static final String BEGIN_TITLE = "->开始准备处理自动分配";
+
+    public static final String EXCEPTION_DATA = "错误信息:";
+
+    public static final String EXCEPTION_CREATE_TXT_TITLE = "->分配时发生异常!";
+
+    @Async
+    public abstract void assignTask(Map<String, Object> map) throws Exception;
+}

+ 70 - 0
src/main/java/com/qmth/exam/reserve/template/execute/AutoAssignStudentService.java

@@ -0,0 +1,70 @@
+package com.qmth.exam.reserve.template.execute;
+
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskResult;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import com.qmth.exam.reserve.service.AsyncTaskService;
+import com.qmth.exam.reserve.service.StudentAutoAssignService;
+import com.qmth.exam.reserve.template.assign.AsyncAutoAssignTaskTemplate;
+import com.qmth.exam.reserve.util.DateUtil;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * @Description 自动分配考生
+ */
+@Service
+public class AutoAssignStudentService extends AsyncAutoAssignTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(AutoAssignStudentService.class);
+
+    public static final String OBJ_TITLE = "未完成预约的考生";
+
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @Autowired
+    private StudentAutoAssignService studentAutoAssignService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void assignTask(Map<String, Object> map) {
+        AsyncTaskEntity task = (AsyncTaskEntity) map.get(Constants.ASYNC_TASK);
+        StringJoiner summary = new StringJoiner("\n").add(
+                MessageFormat.format("{0}{1}{2}", DateFormatUtils.format(new Date(), DateUtil.LongDateString), BEGIN_TITLE, OBJ_TITLE));
+        task.setStatus(AsyncTaskStatus.RUNNING);
+        task.setSummary(summary.toString());
+        asyncTaskService.updateById(task);
+        try {
+            Long taskId = Long.parseLong(map.get("taskId").toString());
+            Long operateId = Long.parseLong(map.get("operateId").toString());
+            //自动分配
+            String message = studentAutoAssignService.autoAssign(taskId, operateId);
+            summary.add(message);
+
+            task.setSummary(summary.toString());
+            task.setImportFileName(AsyncTaskType.AUTO_ASSIGN.getTitle());
+            task.setResult(AsyncTaskResult.SUCCESS);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            task.setResult(AsyncTaskResult.ERROR);
+            summary.add(MessageFormat.format("{0}{1}{2}{3}", DateFormatUtils.format(new Date(), DateUtil.LongDateString),
+                    EXCEPTION_CREATE_TXT_TITLE, EXCEPTION_DATA, e.getMessage()));
+            task.setSummary(summary.toString());
+        } finally {
+            task.setStatus(AsyncTaskStatus.FINISH);
+            asyncTaskService.updateById(task);
+        }
+    }
+}

+ 21 - 13
src/main/resources/mapper/StudentMapper.xml

@@ -7,11 +7,15 @@
             s.id = #{studentId}
     </update>
     <select id="listNoFinishStudent" resultType="com.qmth.exam.reserve.entity.StudentEntity">
-        SELECT s.id,s.apply_number,s.category_id
+        SELECT s.id, s.apply_number, s.category_id
         FROM t_student s
-                 LEFT JOIN (SELECT student_id, count(1) nums
-                            FROM t_student_apply a
-                            WHERE a.cancel = #{cancel}
+                 LEFT JOIN (SELECT student_id,
+                                   count(1) nums
+                            FROM t_student_apply sa,
+                                 t_student s
+                            WHERE sa.student_id = s.id
+                              AND sa.cancel = #{cancel}
+                              AND apply_task_id = #{taskId}
                             GROUP BY student_id) ts ON ts.student_id = s.id
         WHERE apply_task_id = #{taskId}
           AND s.apply_number - ifnull(ts.nums, 0) > 0
@@ -38,17 +42,21 @@
     </select>
 
     <select id="listNoFinishCategory" resultType="com.qmth.exam.reserve.bean.stdapply.CategoryVO">
-        SELECT DISTINCT
-            s.category_id id
-        FROM
-            t_category g,
-            t_student s
-                LEFT JOIN ( SELECT student_id, count( 1 ) nums FROM t_student_apply a WHERE a.cancel = #{cancel} GROUP BY student_id ) ts ON ts.student_id = s.id
-        WHERE
-            apply_task_id =  #{taskId}
+        SELECT DISTINCT s.category_id AS id
+        FROM t_category g,
+             t_student s
+                 LEFT JOIN (SELECT student_id,
+                                   count(1) nums
+                            FROM t_student_apply sa,
+                                 t_student s
+                            WHERE sa.student_id = s.id
+                              AND sa.cancel = #{cancel}
+                              AND apply_task_id = #{taskId}
+                            GROUP BY student_id) ts ON ts.student_id = s.id
+        WHERE apply_task_id = #{taskId}
           AND g.id = s.category_id
           AND g.ENABLE = 1
-          AND s.apply_number - ifnull( ts.nums, 0 ) > 0
+          AND s.apply_number - ifnull(ts.nums, 0) > 0
     </select>
 
     <select id="pageStudent" resultType="com.qmth.exam.reserve.bean.student.StudentVO">

+ 1 - 0
src/main/resources/mapper/TimePeriodMapper.xml

@@ -50,5 +50,6 @@
             t_time_period tp
         WHERE
           tp.apply_task_id = #{taskId}
+        order by tp.start_time
     </select>
 </mapper>