|
@@ -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.concurrent.service.ConcurrentService;
|
|
import com.qmth.boot.core.exception.StatusException;
|
|
import com.qmth.boot.core.exception.StatusException;
|
|
import com.qmth.exam.reserve.bean.apply.ApplyRecordCacheBean;
|
|
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.CacheConstants;
|
|
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
|
|
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.dao.StudentApplyDao;
|
|
import com.qmth.exam.reserve.entity.*;
|
|
import com.qmth.exam.reserve.entity.*;
|
|
import com.qmth.exam.reserve.service.*;
|
|
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.redisson.api.RLock;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
-import org.springframework.scheduling.annotation.Async;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
+import java.text.MessageFormat;
|
|
import java.util.*;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@@ -40,56 +46,70 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
|
|
@Autowired
|
|
@Autowired
|
|
private ExamSiteService examSiteService;
|
|
private ExamSiteService examSiteService;
|
|
|
|
|
|
- @Async
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private TimePeriodService timePeriodService;
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private CategoryCacheService categoryCacheService;
|
|
|
|
+
|
|
|
|
+
|
|
@Override
|
|
@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);
|
|
RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
|
|
|
|
+
|
|
try {
|
|
try {
|
|
if (!lock.tryLock()) {
|
|
if (!lock.tryLock()) {
|
|
- log.warn("获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
|
|
|
|
|
|
+ log.warn("[autoAssign] 获取锁失败,不允许同时执行自动分配!lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
|
|
throw new StatusException("其他老师正在执行自动分配,请不要重复执行!");
|
|
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);
|
|
List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
|
|
|
|
+
|
|
|
|
+ //按照教学点分组
|
|
Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream().collect(Collectors.groupingBy(StudentEntity::getCategoryId));
|
|
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;
|
|
continue;
|
|
}
|
|
}
|
|
- List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
|
|
|
|
|
|
+ List<StudentEntity> teachingStudentList = noFinishApplyMap.get(teachingId);
|
|
for (ExamSiteEntity site : siteList) {
|
|
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) {
|
|
} catch (Exception e) {
|
|
log.error(e.getMessage());
|
|
log.error(e.getMessage());
|
|
throw new StatusException(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);
|
|
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;
|
|
int num = 0;
|
|
- String studentApplyLockKey = null;
|
|
|
|
- RLock studentApplyLock = null;
|
|
|
|
|
|
+ String studentApplyLockKey;
|
|
|
|
+ RLock studentApplyLock;
|
|
for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext(); ) {
|
|
for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext(); ) {
|
|
StudentEntity student = iterator.next();
|
|
StudentEntity student = iterator.next();
|
|
- if (num >= remainNum) break;
|
|
|
|
|
|
+ if (num >= remainNum) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 考生锁
|
|
studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
|
|
studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
|
|
studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
|
|
studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
|
|
try {
|
|
try {
|
|
if (!studentApplyLock.tryLock()) {
|
|
if (!studentApplyLock.tryLock()) {
|
|
- log.warn("获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
|
|
|
|
|
|
+ log.warn("[autoAssign] 获取锁失败,考生在同时操作预约!lockKey:{}", studentApplyLockKey);
|
|
iterator.remove();
|
|
iterator.remove();
|
|
} else {
|
|
} else {
|
|
- log.warn("获取锁成功!lockKey:{}", studentApplyLockKey);
|
|
|
|
|
|
+ log.warn("[autoAssign] 获取锁成功!lockKey:{}", studentApplyLockKey);
|
|
|
|
|
|
- //List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId());
|
|
|
|
|
|
+ // 考生已完成预约的数量
|
|
int haveApplyCount = cacheService.getStudentApplyFinishCount(student.getId());
|
|
int haveApplyCount = cacheService.getStudentApplyFinishCount(student.getId());
|
|
|
|
+ // 还需要预约的数量
|
|
int toApplyNum = student.getApplyNumber() - haveApplyCount;
|
|
int toApplyNum = student.getApplyNumber() - haveApplyCount;
|
|
|
|
+
|
|
|
|
+ if (toApplyNum <= 0) {
|
|
|
|
+ log.warn("[autoAssign] 数据问题, 考生需要预约最大次数:{},已完成预约的数量:{} ", student.getApplyNumber(), haveApplyCount);
|
|
|
|
+ }
|
|
|
|
+
|
|
if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
|
|
if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
|
|
|
|
+ /*
|
|
|
|
+ //写入数据库
|
|
StudentApplyEntity studentApply = new StudentApplyEntity();
|
|
StudentApplyEntity studentApply = new StudentApplyEntity();
|
|
studentApply.setStudentId(student.getId());
|
|
studentApply.setStudentId(student.getId());
|
|
studentApply.setExamSiteId(siteId);
|
|
studentApply.setExamSiteId(siteId);
|
|
studentApply.setCancel(Boolean.FALSE);
|
|
studentApply.setCancel(Boolean.FALSE);
|
|
studentApply.setTimePeriodId(timeId);
|
|
studentApply.setTimePeriodId(timeId);
|
|
- studentApply.setOperateId(userId);
|
|
|
|
|
|
+ studentApply.setOperateId(operateId);
|
|
|
|
|
|
StudentApplyEntity existStudentApply = findStudentApply(studentApply);
|
|
StudentApplyEntity existStudentApply = findStudentApply(studentApply);
|
|
if (existStudentApply != null) {
|
|
if (existStudentApply != null) {
|
|
@@ -158,30 +202,31 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
|
|
baseMapper.updateById(existStudentApply);
|
|
baseMapper.updateById(existStudentApply);
|
|
} else {
|
|
} else {
|
|
baseMapper.insert(studentApply);
|
|
baseMapper.insert(studentApply);
|
|
- }
|
|
|
|
-
|
|
|
|
- num++;
|
|
|
|
- if (student.getApplyNumber() - (haveApplyCount + 1) == 0) {
|
|
|
|
- iterator.remove();
|
|
|
|
- }
|
|
|
|
|
|
+ }*/
|
|
|
|
|
|
ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
|
|
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.setCancel(Boolean.FALSE);
|
|
- bean.setOperateId(userId);
|
|
|
|
|
|
+ bean.setOperateId(operateId);
|
|
bean.setOperateTime(System.currentTimeMillis());
|
|
bean.setOperateTime(System.currentTimeMillis());
|
|
|
|
|
|
// 先推送至预约队列
|
|
// 先推送至预约队列
|
|
cacheService.pushStudentApplyRecordQueue(bean);
|
|
cacheService.pushStudentApplyRecordQueue(bean);
|
|
cacheService.saveStudentApplyRecord(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) {
|
|
} catch (Exception e) {
|
|
- log.error("自动安排预约失败,错误信息:{}", e.getMessage());
|
|
|
|
- throw new StatusException("自动安排预约失败,请稍后再试!");
|
|
|
|
|
|
+ throw new StatusException("自动安排预约失败,失败原因:" + e.getMessage());
|
|
} finally {
|
|
} finally {
|
|
try {
|
|
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) {
|
|
private List<StudentApplyEntity> listStudentApply(Long stdId) {
|
|
@@ -212,13 +268,12 @@ public class StudentAutoAssignServiceImpl extends ServiceImpl<StudentApplyDao, S
|
|
return baseMapper.selectOne(lm);
|
|
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<>();
|
|
LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
|
|
wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
|
|
wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
|
|
wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
|
|
wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
|
|
- wrapper.eq(StudentApplyEntity::getStudentId, studentId);
|
|
|
|
wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
|
|
wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
|
|
- StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
|
|
|
|
- return studentApply != null;
|
|
|
|
|
|
+ return baseMapper.selectCount(wrapper);
|
|
}
|
|
}
|
|
|
|
+
|
|
}
|
|
}
|