|
@@ -1,48 +1,21 @@
|
|
|
package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.Date;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.HashSet;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Random;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-
|
|
|
-import org.apache.commons.collections.CollectionUtils;
|
|
|
-import org.apache.commons.lang3.StringUtils;
|
|
|
-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 cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
|
|
|
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
|
|
|
import cn.com.qmth.examcloud.api.commons.security.bean.User;
|
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
import cn.com.qmth.examcloud.commons.util.UUID;
|
|
|
import cn.com.qmth.examcloud.commons.util.Util;
|
|
|
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.base.enums.ExamProperties;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.base.helper.ExamCacheTransferHelper;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.base.utils.CommonUtil;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.base.utils.QuestionTypeUtil;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.bean.CheckExamInProgressInfo;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.bean.ExamRecordPaperStruct;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.bean.StartExamInfo;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamFaceLivenessVerifyService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordPaperStructService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordQuestionsService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.ExamingSessionService;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.FaceBiopsyService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.*;
|
|
|
import cn.com.qmth.examcloud.core.oe.task.api.ExamCaptureCloudService;
|
|
|
import cn.com.qmth.examcloud.core.oe.task.api.request.SaveExamCaptureSyncCompareResultReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.task.api.request.UpdateExamCaptureQueuePriorityReq;
|
|
|
import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
|
|
|
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
|
|
|
import cn.com.qmth.examcloud.examwork.api.request.GetExamPropertyReq;
|
|
|
import cn.com.qmth.examcloud.examwork.api.response.GetExamPropertyResp;
|
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
|
|
@@ -52,22 +25,30 @@ import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrap
|
|
|
import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
|
|
|
import cn.com.qmth.examcloud.reports.commons.bean.OnlineExamStudentReport;
|
|
|
import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
|
|
|
+import cn.com.qmth.examcloud.support.Constants;
|
|
|
import cn.com.qmth.examcloud.support.cache.CacheHelper;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExamOrgSettingsCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExamStudentSettingsCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigDetailCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigPaperCacheBean;
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.*;
|
|
|
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
|
|
|
import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
|
|
|
import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
|
|
|
import cn.com.qmth.examcloud.support.enums.HandInExamType;
|
|
|
import cn.com.qmth.examcloud.support.examing.ExamRecordData;
|
|
|
import cn.com.qmth.examcloud.support.examing.ExamingSession;
|
|
|
import cn.com.qmth.examcloud.support.examing.ExamingStatus;
|
|
|
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
|
|
|
import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
|
import cn.com.qmth.examcloud.web.exception.SequenceLockException;
|
|
|
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
|
|
|
+import org.apache.commons.collections.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+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.util.*;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
* @author chenken
|
|
@@ -97,10 +78,10 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
@Autowired
|
|
|
private ExamCloudService examCloudService;
|
|
|
-
|
|
|
+
|
|
|
@Autowired
|
|
|
private FaceBiopsyService faceBiopsyService;
|
|
|
-
|
|
|
+
|
|
|
@Autowired
|
|
|
private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
|
|
|
|
|
@@ -133,14 +114,14 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
// 检查并获取考试信息
|
|
|
startTime = System.currentTimeMillis();
|
|
|
- ExamBean examBean = checkExam(examingSession.getExamId(), examingSession.getStudentId());
|
|
|
+ ExamSettingsCacheBean examBean = checkExam(examingSession.getExamId(), examingSession.getStudentId());
|
|
|
if (log.isDebugEnabled()) {
|
|
|
log.debug("2 检查并获取考试信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
}
|
|
|
|
|
|
// 检查并获取课程信息
|
|
|
startTime = System.currentTimeMillis();
|
|
|
- CourseBean courseBean = checkCourse(examingSession.getCourseId());
|
|
|
+ CourseCacheBean courseBean = checkCourse(examingSession.getCourseId());
|
|
|
if (log.isDebugEnabled()) {
|
|
|
log.debug("3 检查并获取课程信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
}
|
|
@@ -234,14 +215,54 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
/**
|
|
|
* 交卷
|
|
|
*
|
|
|
- * @param examRecordDataId
|
|
|
- * 考试记录id
|
|
|
- * @param handInExamType
|
|
|
- * 交卷类型
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @param handInExamType 交卷类型
|
|
|
*/
|
|
|
+ @Transactional
|
|
|
@Override
|
|
|
public void handInExam(Long examRecordDataId, HandInExamType handInExamType) {
|
|
|
+ // 此锁是为了避免自动交卷服务与断点续考交卷或活检失败交卷,同一时刻交卷争抢资源导致死锁
|
|
|
+ String sequenceLockKey = Constants.HAND_IN_EXAM_LOCK_PREFIX + examRecordDataId;
|
|
|
+ // 系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+
|
|
|
+ Long studentId = examRecordData.getStudentId();
|
|
|
+ if (handInExamType == HandInExamType.MANUAL) {
|
|
|
+
|
|
|
+ // 得到考试时长,校验是否达到冻结时间
|
|
|
+ long usedExamTime = checkAndComputeExamDuration(studentId);
|
|
|
+ examRecordData.setUsedExamTime(usedExamTime);
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
|
|
|
+ examRecordData.setEndTime(new Date());
|
|
|
+
|
|
|
+ // 手工手卷时,如果开启人脸检测,则更新抓拍队列优先级
|
|
|
+ Long rootOrgId = examRecordData.getRootOrgId();
|
|
|
+ Long examId = examRecordData.getExamId();
|
|
|
+
|
|
|
+ if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
|
|
|
+ // 更新照片抓拍队列优先级为高优先级
|
|
|
+ UpdateExamCaptureQueuePriorityReq req =new UpdateExamCaptureQueuePriorityReq();
|
|
|
+ req.setExamRecordDataId(examRecordDataId);
|
|
|
+ req.setPriority(Constants.PROCESS_CAPTURE_HIGH_PRIORITY);
|
|
|
+ examCaptureCloudService.updateExamCaptureQueuePriority(req);
|
|
|
+ }
|
|
|
+ } else if (handInExamType == HandInExamType.AUTO) {
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_AUTO_HAND_IN);
|
|
|
+ examRecordData.setCleanTime(new Date());
|
|
|
+ } else {
|
|
|
+ throw new StatusException("201101", "暂不支持的交卷类型");
|
|
|
+ }
|
|
|
+
|
|
|
+ //标记当前状态为手动交卷
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
|
|
|
|
|
|
+ // 保存考试记录
|
|
|
+ examRecordDataService.saveExamRecordDataCache(examRecordDataId,examRecordData);
|
|
|
+
|
|
|
+ // 删除redis会话
|
|
|
+ examingSessionService.deleteExamingSession(studentId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -252,10 +273,10 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @param studentId
|
|
|
* @return
|
|
|
*/
|
|
|
- private ExamBean checkExam(Long examId, Long studentId) {
|
|
|
+ private ExamSettingsCacheBean checkExam(Long examId, Long studentId) {
|
|
|
|
|
|
// 学习中心特殊考试配置(是否禁考,开考时间可以特殊设置)
|
|
|
- ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
|
|
|
+ ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
|
|
|
// 如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
|
|
|
ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(examId,
|
|
|
ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
|
|
@@ -294,8 +315,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
return examBean;
|
|
|
}
|
|
|
|
|
|
- private CourseBean checkCourse(Long courseId) {
|
|
|
- CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
|
|
|
+ private CourseCacheBean checkCourse(Long courseId) {
|
|
|
+ CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
|
|
|
if (!courseBean.getEnable()) {
|
|
|
throw new StatusException("3001", "该课程已被禁用");
|
|
|
}
|
|
@@ -447,8 +468,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession, ExamBean examBean,
|
|
|
- CourseBean courseBean) {
|
|
|
+ private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession, ExamSettingsCacheBean examBean,
|
|
|
+ CourseCacheBean courseBean) {
|
|
|
StartExamInfo startExamInfo = new StartExamInfo();
|
|
|
startExamInfo.setExamRecordDataId(examRecordDataId);
|
|
|
startExamInfo.setCourseName(courseBean.getName());
|
|
@@ -495,7 +516,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @param examBean
|
|
|
*/
|
|
|
public void initializeExamRecordSession(ExamingSession examSessionInfo, ExamRecordData examRecordData,
|
|
|
- final ExamBean examBean) {
|
|
|
+ final ExamSettingsCacheBean examBean) {
|
|
|
examSessionInfo.setExamRecordDataId(examRecordData.getId());
|
|
|
examSessionInfo.setStartTime(examRecordData.getStartTime().getTime());
|
|
|
examSessionInfo.setExamType(examBean.getExamType());
|
|
@@ -675,4 +696,35 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
// 返回考试剩余时间
|
|
|
return examSessionInfo.getExamDuration() - (examSessionInfo.getCost() * 1000);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算考试时长 校验是否达到冻结时间
|
|
|
+ *
|
|
|
+ * @param studentId 学生id
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Long checkAndComputeExamDuration(Long studentId) {
|
|
|
+
|
|
|
+ // 获取考试会话,判断考生是否已结束考试(二次校验)
|
|
|
+ ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
|
|
|
+ if (examingSession == null) {
|
|
|
+ throw new StatusException("oestudent-100100", "考试会话已过期");
|
|
|
+ }
|
|
|
+ Long examUsedMilliSeconds = examingSession.getCost() * 1000;
|
|
|
+ // 如果没有超过冻结时间,抛出异常
|
|
|
+ if (examingSession.getExamType().equals(ExamType.ONLINE.name())) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examingSession.getExamRecordDataId());
|
|
|
+
|
|
|
+ if (examRecordData != null && examRecordData.getIsExceed() != null && examRecordData.getIsExceed()) {// 超过断点最大次数的不校验冻结时间
|
|
|
+ return examUsedMilliSeconds;
|
|
|
+ }
|
|
|
+ long freezeTime = examingSession.getFreezeTime() * 60 * 1000;
|
|
|
+ if (examUsedMilliSeconds < freezeTime) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkAndComputeExamDuration-exception",
|
|
|
+ "开考" + examingSession.getFreezeTime() + "分钟后才能交卷");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return examUsedMilliSeconds;
|
|
|
+ }
|
|
|
}
|