|
@@ -9,6 +9,7 @@ import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.Set;
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
import org.apache.commons.collections.CollectionUtils;
|
|
import org.apache.commons.collections.CollectionUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
@@ -21,11 +22,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|
import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
|
|
import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
|
|
import cn.com.qmth.examcloud.api.commons.security.bean.User;
|
|
import cn.com.qmth.examcloud.api.commons.security.bean.User;
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
|
+import cn.com.qmth.examcloud.commons.util.Util;
|
|
import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
|
|
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.enums.ExamProperties;
|
|
import cn.com.qmth.examcloud.core.oe.student.base.helper.ExamCacheTransferHelper;
|
|
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.CommonUtil;
|
|
import cn.com.qmth.examcloud.core.oe.student.base.utils.QuestionTypeUtil;
|
|
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.StartExamInfo;
|
|
import cn.com.qmth.examcloud.core.oe.student.bean.StartExamInfo;
|
|
import cn.com.qmth.examcloud.core.oe.student.dao.enums.HandInExamType;
|
|
import cn.com.qmth.examcloud.core.oe.student.dao.enums.HandInExamType;
|
|
import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
|
|
import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
|
|
@@ -35,7 +38,10 @@ 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.ExamingSessionService;
|
|
import cn.com.qmth.examcloud.core.oe.task.api.ExamCaptureCloudService;
|
|
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.SaveExamCaptureSyncCompareResultReq;
|
|
|
|
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
|
|
import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
|
|
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;
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
|
|
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
|
|
@@ -51,10 +57,13 @@ 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.ExtractConfigDetailCacheBean;
|
|
import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigPaperCacheBean;
|
|
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.SysPropertyCacheBean;
|
|
|
|
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
|
|
|
|
+import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
|
|
import cn.com.qmth.examcloud.support.examing.ExamRecordData;
|
|
import cn.com.qmth.examcloud.support.examing.ExamRecordData;
|
|
import cn.com.qmth.examcloud.support.examing.ExamingSession;
|
|
import cn.com.qmth.examcloud.support.examing.ExamingSession;
|
|
import cn.com.qmth.examcloud.support.examing.ExamingStatus;
|
|
import cn.com.qmth.examcloud.support.examing.ExamingStatus;
|
|
import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
|
|
+import cn.com.qmth.examcloud.web.exception.SequenceLockException;
|
|
|
|
|
|
/**
|
|
/**
|
|
* @author chenken
|
|
* @author chenken
|
|
@@ -78,11 +87,13 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
private ExamRecordQuestionsService examRecordQuestionsService;
|
|
private ExamRecordQuestionsService examRecordQuestionsService;
|
|
-
|
|
|
|
|
|
+
|
|
@Autowired
|
|
@Autowired
|
|
private ExamCaptureCloudService examCaptureCloudService;
|
|
private ExamCaptureCloudService examCaptureCloudService;
|
|
-
|
|
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private ExamCloudService examCloudService;
|
|
|
|
+
|
|
@Transactional
|
|
@Transactional
|
|
@Override
|
|
@Override
|
|
public StartExamInfo startExam(Long examStudentId, User user) {
|
|
public StartExamInfo startExam(Long examStudentId, User user) {
|
|
@@ -143,8 +154,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
// 生成考试记录
|
|
// 生成考试记录
|
|
startTime = System.currentTimeMillis();
|
|
startTime = System.currentTimeMillis();
|
|
- ExamRecordData examRecordData = examRecordDataService.createExamRecordData(examingSession, examBean,
|
|
|
|
- courseBean, paperId);
|
|
|
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.createExamRecordData(examingSession, examBean, courseBean,
|
|
|
|
+ paperId);
|
|
|
|
|
|
// 如果开启人脸比对,将同步人脸比对结果存储到抓后结果表中
|
|
// 如果开启人脸比对,将同步人脸比对结果存储到抓后结果表中
|
|
Long rootOrgId = examRecordData.getRootOrgId();
|
|
Long rootOrgId = examRecordData.getRootOrgId();
|
|
@@ -211,8 +222,10 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
/**
|
|
/**
|
|
* 交卷
|
|
* 交卷
|
|
*
|
|
*
|
|
- * @param examRecordDataId 考试记录id
|
|
|
|
- * @param handInExamType 交卷类型
|
|
|
|
|
|
+ * @param examRecordDataId
|
|
|
|
+ * 考试记录id
|
|
|
|
+ * @param handInExamType
|
|
|
|
+ * 交卷类型
|
|
*/
|
|
*/
|
|
@Override
|
|
@Override
|
|
public void handInExam(Long examRecordDataId, HandInExamType handInExamType) {
|
|
public void handInExam(Long examRecordDataId, HandInExamType handInExamType) {
|
|
@@ -447,8 +460,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
.getCachedExamProperty(examId, studentId, ExamProperties.FACE_VERIFY_START_MINUTE.name())
|
|
.getCachedExamProperty(examId, studentId, ExamProperties.FACE_VERIFY_START_MINUTE.name())
|
|
.getValue();
|
|
.getValue();
|
|
if (CommonUtil.isBlank(startMinuteStr)) {
|
|
if (CommonUtil.isBlank(startMinuteStr)) {
|
|
- throw new StatusException("5001",
|
|
|
|
- ExamProperties.FACE_VERIFY_START_MINUTE.getDesc() + "未设置");
|
|
|
|
|
|
+ throw new StatusException("5001", ExamProperties.FACE_VERIFY_START_MINUTE.getDesc() + "未设置");
|
|
}
|
|
}
|
|
Integer faceVerifyStartMinute = Integer.valueOf(startMinuteStr);
|
|
Integer faceVerifyStartMinute = Integer.valueOf(startMinuteStr);
|
|
|
|
|
|
@@ -456,8 +468,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
String endMinuteStr = ExamCacheTransferHelper
|
|
String endMinuteStr = ExamCacheTransferHelper
|
|
.getCachedExamProperty(examId, studentId, ExamProperties.FACE_VERIFY_END_MINUTE.name()).getValue();
|
|
.getCachedExamProperty(examId, studentId, ExamProperties.FACE_VERIFY_END_MINUTE.name()).getValue();
|
|
if (CommonUtil.isBlank(endMinuteStr)) {
|
|
if (CommonUtil.isBlank(endMinuteStr)) {
|
|
- throw new StatusException("5002",
|
|
|
|
- ExamProperties.FACE_VERIFY_END_MINUTE.getDesc() + "未设置");
|
|
|
|
|
|
+ throw new StatusException("5002", ExamProperties.FACE_VERIFY_END_MINUTE.getDesc() + "未设置");
|
|
}
|
|
}
|
|
Integer faceVerifyEndMinute = Integer.valueOf(endMinuteStr);
|
|
Integer faceVerifyEndMinute = Integer.valueOf(endMinuteStr);
|
|
return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute);
|
|
return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute);
|
|
@@ -482,8 +493,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
examSessionInfo.getStudentId(), ExamProperties.EXAM_RECONNECT_TIME.name()).getValue();
|
|
examSessionInfo.getStudentId(), ExamProperties.EXAM_RECONNECT_TIME.name()).getValue();
|
|
log.debug("11.2 断点时间:" + examReconnectTimeStr);
|
|
log.debug("11.2 断点时间:" + examReconnectTimeStr);
|
|
if (CommonUtil.isBlank(examReconnectTimeStr)) {
|
|
if (CommonUtil.isBlank(examReconnectTimeStr)) {
|
|
- throw new StatusException("6001",
|
|
|
|
- ExamProperties.EXAM_RECONNECT_TIME.getDesc() + "未设置");
|
|
|
|
|
|
+ throw new StatusException("6001", ExamProperties.EXAM_RECONNECT_TIME.getDesc() + "未设置");
|
|
}
|
|
}
|
|
examSessionInfo.setExamReconnectTime(Integer.valueOf(examReconnectTimeStr));
|
|
examSessionInfo.setExamReconnectTime(Integer.valueOf(examReconnectTimeStr));
|
|
// FREEZE_TIME:冻结时间
|
|
// FREEZE_TIME:冻结时间
|
|
@@ -491,13 +501,136 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
examSessionInfo.getStudentId(), ExamProperties.FREEZE_TIME.name()).getValue();
|
|
examSessionInfo.getStudentId(), ExamProperties.FREEZE_TIME.name()).getValue();
|
|
log.debug("11.3 冻结时间:" + freezeTimeStr);
|
|
log.debug("11.3 冻结时间:" + freezeTimeStr);
|
|
if (CommonUtil.isBlank(freezeTimeStr)) {
|
|
if (CommonUtil.isBlank(freezeTimeStr)) {
|
|
- throw new StatusException("6002",
|
|
|
|
- ExamProperties.FREEZE_TIME.getDesc() + "未设置");
|
|
|
|
|
|
+ throw new StatusException("6002", ExamProperties.FREEZE_TIME.getDesc() + "未设置");
|
|
}
|
|
}
|
|
examSessionInfo.setFreezeTime(Integer.valueOf(freezeTimeStr));
|
|
examSessionInfo.setFreezeTime(Integer.valueOf(freezeTimeStr));
|
|
examSessionInfo.setExamingStatus(ExamingStatus.FORMAL);
|
|
examSessionInfo.setExamingStatus(ExamingStatus.FORMAL);
|
|
log.debug("11.4 开始保存考试会话...studentId=" + examSessionInfo.getStudentId());
|
|
log.debug("11.4 开始保存考试会话...studentId=" + examSessionInfo.getStudentId());
|
|
- examingSessionService.saveExamingSession(examSessionInfo.getStudentId(), examSessionInfo,-1);
|
|
|
|
|
|
+ examingSessionService.saveExamingSession(examSessionInfo.getStudentId(), examSessionInfo);
|
|
log.debug("11.5 保存考试会话结束 ");
|
|
log.debug("11.5 保存考试会话结束 ");
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public CheckExamInProgressInfo checkExamInProgress(Long studentId) {
|
|
|
|
+ ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
|
|
|
|
+ if (examSessionInfo == null || ExamingStatus.INFORMAL.equals(examSessionInfo.getExamingStatus())) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ // 检查考试会话是否存在,或者是否失效,如果没有失效,则返回考试中的考试记录实体,否则直接返回null
|
|
|
|
+ ExamRecordData examingRecord = checkExamSession(examSessionInfo, studentId);
|
|
|
|
+ if (examingRecord == null) {
|
|
|
|
+ return null;
|
|
|
|
+ } else {
|
|
|
|
+ Integer maxInterruptNum = getMaxInterruptNum(examSessionInfo.getExamId(), examSessionInfo.getRootOrgId(),
|
|
|
|
+ examSessionInfo.getOrgId());
|
|
|
|
+ CheckExamInProgressInfo checkExamInProgressInfo = new CheckExamInProgressInfo();
|
|
|
|
+
|
|
|
|
+ if ((examingRecord.getIsExceed() == null || !examingRecord.getIsExceed())
|
|
|
|
+ && examingRecord.getContinuedCount().intValue() < maxInterruptNum.intValue()) {// 未达到最大断点次数,可继续断点一次
|
|
|
|
+ // 断点续考次数自增
|
|
|
|
+ int continutedCount = examingRecord.getContinuedCount() == null ? 0
|
|
|
|
+ : examingRecord.getContinuedCount().intValue();
|
|
|
|
+ examingRecord.setContinuedCount(continutedCount + 1);
|
|
|
|
+ examingRecord.setIsContinued(true);
|
|
|
|
+ examingRecord.setIsExceed(false);
|
|
|
|
+ checkExamInProgressInfo.setIsExceed(false);
|
|
|
|
+ } else {
|
|
|
|
+ examingRecord.setIsExceed(true);
|
|
|
|
+ checkExamInProgressInfo.setIsExceed(true);
|
|
|
|
+ }
|
|
|
|
+ // 更新考试中的断点续考属性
|
|
|
|
+ examRecordDataService.saveExamRecordDataCache(examingRecord.getId(), examingRecord);
|
|
|
|
+
|
|
|
|
+ checkExamInProgressInfo.setExamRecordDataId(examingRecord.getId());
|
|
|
|
+ checkExamInProgressInfo.setExamId(examSessionInfo.getExamId());
|
|
|
|
+ checkExamInProgressInfo.setUsedTime(examSessionInfo.getCost());
|
|
|
|
+ checkExamInProgressInfo.setMaxInterruptNum(maxInterruptNum);
|
|
|
|
+ checkExamInProgressInfo.setInterruptNum(examingRecord.getContinuedCount());
|
|
|
|
+
|
|
|
|
+ // 断点续考时重新计算活体检测的分钟数
|
|
|
|
+ Integer faceVerifyMinute = null;
|
|
|
|
+ FaceBiopsyScheme faceBiopsyScheme = FaceBiopsyHelper.getFaceBiopsyScheme(examSessionInfo.getRootOrgId());
|
|
|
|
+
|
|
|
|
+ // 如果是新活体检测方案,则使用新的计算方案计算活检开始时间
|
|
|
|
+ if (faceBiopsyScheme == FaceBiopsyScheme.NEW) {
|
|
|
|
+ faceVerifyMinute = faceBiopsyService.calculateFaceBiopsyStartMinute(examingRecord.getId());
|
|
|
|
+ }else {// 非新活检,默认使用旧的活检计算方式
|
|
|
|
+ faceVerifyMinute = examFaceLivenessVerifyService.getFaceLivenessVerifyMinute(
|
|
|
|
+ examSessionInfo.getRootOrgId(), examSessionInfo.getOrgId(), examSessionInfo.getExamId(),
|
|
|
|
+ studentId, examSessionInfo.getExamRecordDataId(), examSessionInfo.getHeartbeat());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ checkExamInProgressInfo.setFaceVerifyMinute(faceVerifyMinute);
|
|
|
|
+ return checkExamInProgressInfo;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private ExamRecordData checkExamSession(ExamingSession examSessionInfo, Long studentId) {
|
|
|
|
+ ExamRecordData examingRecord = examRecordDataService
|
|
|
|
+ .getExamRecordDataCache(examSessionInfo.getExamRecordDataId());
|
|
|
|
+ if (examingRecord == null) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果考试时间结束,自动交卷
|
|
|
|
+ if (examSessionInfo.getExamDuration() <= examSessionInfo.getCost()) {
|
|
|
|
+ delayHandInExamIfLocked(examingRecord.getId());
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果已经过了断点续考时间,自动交卷
|
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
|
+ if (now - examSessionInfo.getActiveTime() >= examSessionInfo.getExamReconnectTime().intValue() * 60 * 1000) {
|
|
|
|
+ delayHandInExamIfLocked(examingRecord.getId());
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ return examingRecord;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 如果有序列化锁,则延迟交卷
|
|
|
|
+ *
|
|
|
|
+ * @param examRecordDataId
|
|
|
|
+ * 考试记录id
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private void delayHandInExamIfLocked(Long examRecordDataId) {
|
|
|
|
+ try {
|
|
|
|
+ handInExam(examRecordDataId, HandInExamType.AUTO);
|
|
|
|
+ } catch (SequenceLockException e) {
|
|
|
|
+ // 如果发现自动服务正在交卷,则重试1500毫秒获取考试记录状态,判断是否已交卷
|
|
|
|
+ int loopTimes = 0;
|
|
|
|
+ while (loopTimes <= 15) {
|
|
|
|
+ loopTimes++;
|
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
|
+ if (examRecordData == null) {
|
|
|
|
+ throw new StatusException("7001", "考试信息不存在");
|
|
|
|
+ }
|
|
|
|
+ // 1500毫秒内如果交卷成功,则退出循环
|
|
|
|
+ if (examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_ING) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ Util.sleep(TimeUnit.MILLISECONDS, 100);
|
|
|
|
+ }
|
|
|
|
+ throw e;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Integer getMaxInterruptNum(Long examId, Long rootOrgId, Long orgId) {
|
|
|
|
+ GetExamPropertyReq req = new GetExamPropertyReq();
|
|
|
|
+ req.setExamId(examId);
|
|
|
|
+ req.setRootOrgId(rootOrgId);
|
|
|
|
+ req.setOrgId(orgId);
|
|
|
|
+ req.setKey(ExamProperties.MAX_INTERRUPT_NUM.name());
|
|
|
|
+ GetExamPropertyResp res = examCloudService.getExamProperty(req);
|
|
|
|
+ Integer ret = 100;
|
|
|
|
+ if (StringUtils.isNoneBlank(res.getValue())) {
|
|
|
|
+ try {
|
|
|
|
+ ret = Integer.valueOf(res.getValue());
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
+ log.error("MaxInterruptNum is not a number,return default value:100", e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
}
|
|
}
|