|
@@ -3,6 +3,7 @@ package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
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.enums.ExamStageStartExamStatus;
|
|
import cn.com.qmth.examcloud.api.commons.enums.ExamStageStartExamStatus;
|
|
import cn.com.qmth.examcloud.api.commons.enums.ExamType;
|
|
import cn.com.qmth.examcloud.api.commons.enums.ExamType;
|
|
|
|
+import cn.com.qmth.examcloud.api.commons.enums.SubmitType;
|
|
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.api.commons.security.bean.UserType;
|
|
import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
@@ -309,17 +310,57 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public void startAnswer(Long examRecordDataId) {
|
|
|
|
|
|
+ public StartAnswerInfo startAnswer(Long examRecordDataId) {
|
|
Date now = new Date();
|
|
Date now = new Date();
|
|
|
|
|
|
- ExamRecordDataEntity examRecordDataEntity =
|
|
|
|
- GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
|
|
|
|
- if (null == examRecordDataEntity) {
|
|
|
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
|
+ if (null == examRecordData) {
|
|
throw new StatusException("100001", "找不到相关考试记录");
|
|
throw new StatusException("100001", "找不到相关考试记录");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ExamingSession examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
|
|
|
|
+ if (null == examingSession) {
|
|
|
|
+ throw new StatusException("100002", "无效的会话,请离开考试");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ StartAnswerInfo resultInfo = new StartAnswerInfo();
|
|
|
|
+ resultInfo.setExamRecordDataId(examRecordDataId);
|
|
|
|
+
|
|
|
|
+ //第一次开考,更新考试记录中的开始答题时间
|
|
|
|
+ if (null == examRecordData.getIsContinued() || !examRecordData.getIsContinued()) {
|
|
|
|
+ //如果已经调用过开始作答接口,则不做任何处理
|
|
|
|
+ if (null != examRecordData.getStartTime()) {
|
|
|
|
+ examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
|
|
|
|
+ resultInfo.setDuration(examingSession.getExamDuration());//直接返回考试会话中的考试时长
|
|
|
|
+ return resultInfo;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //更新考试记录缓存
|
|
|
|
+ examRecordData.setLastActiveTime(now);
|
|
|
|
+ examRecordData.setStartTime(now);
|
|
|
|
+ examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
|
|
|
|
+
|
|
|
|
+ //更新考试会话中的作答时间,实际考试总时长
|
|
|
|
+ examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
|
|
|
|
+ examingSession.setStartTime(now.getTime());
|
|
|
|
+ long actualExamTotalMilliSeconds = calcExamTotalMilliSeconds(examRecordDataId);
|
|
|
|
+ examingSession.setExamDuration(actualExamTotalMilliSeconds);
|
|
|
|
+ examingSessionService.saveExamingSession(examRecordData.getStudentId(), examingSession);
|
|
|
|
+
|
|
|
|
+ resultInfo.setDuration(actualExamTotalMilliSeconds);
|
|
|
|
+ return resultInfo;
|
|
|
|
+ }
|
|
|
|
+
|
|
//如果是断点续考,则需要更新断点续考表中开始答题时间
|
|
//如果是断点续考,则需要更新断点续考表中开始答题时间
|
|
- if (null != examRecordDataEntity.getIsContinued() && examRecordDataEntity.getIsContinued()) {
|
|
|
|
|
|
+ else {
|
|
|
|
+ //如果断点续考之前未成功调用过开始答题接口,则需要首先更新考试记录中的首次答题时间
|
|
|
|
+ if (null == examRecordData.getStartTime()) {
|
|
|
|
+ //更新考试记录缓存
|
|
|
|
+ examRecordData.setLastActiveTime(now);
|
|
|
|
+ examRecordData.setStartTime(now);
|
|
|
|
+ examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
|
|
|
|
+ }
|
|
|
|
+
|
|
//获取最新的一条断点记录
|
|
//获取最新的一条断点记录
|
|
ExamContinuedRecordEntity latestExamContinuedRecord =
|
|
ExamContinuedRecordEntity latestExamContinuedRecord =
|
|
examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examRecordDataId);
|
|
examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examRecordDataId);
|
|
@@ -328,31 +369,80 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
}
|
|
}
|
|
|
|
|
|
//如果断点续考后,已经调用过答题接口,则不再处理
|
|
//如果断点续考后,已经调用过答题接口,则不再处理
|
|
- if (null !=latestExamContinuedRecord.getStartTime()) {
|
|
|
|
- return;
|
|
|
|
|
|
+ if (null != latestExamContinuedRecord.getStartTime()) {
|
|
|
|
+ resultInfo.setDuration(examingSession.getExamDuration());
|
|
|
|
+ return resultInfo;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ //更新断点续考中的答题时间
|
|
latestExamContinuedRecord.setStartTime(now);
|
|
latestExamContinuedRecord.setStartTime(now);
|
|
examContinuedRecordRepo.save(latestExamContinuedRecord);
|
|
examContinuedRecordRepo.save(latestExamContinuedRecord);
|
|
|
|
+
|
|
|
|
+ resultInfo.setDuration(examingSession.getExamDuration());
|
|
|
|
+ return resultInfo;
|
|
}
|
|
}
|
|
- //第一次开考,更新考试记录中的开始答题时间
|
|
|
|
- else {
|
|
|
|
- //更新考试记录临时表
|
|
|
|
-// examRecordDataEntity.setStartTime(now);
|
|
|
|
-// examRecordDataEntity.setLastActiveTime(now);
|
|
|
|
-// examRecordDataRepo.save(examRecordDataEntity);
|
|
|
|
|
|
|
|
- ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
|
- //如果已经调用过开始作答接口,则不做任何处理
|
|
|
|
- if (null != examRecordData.getStartTime()) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- //更新考试记录缓存
|
|
|
|
- examRecordData.setLastActiveTime(now);
|
|
|
|
- examRecordData.setStartTime(now);
|
|
|
|
- examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 计算单次考试总时长(毫秒)
|
|
|
|
+ *
|
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private long calcExamTotalMilliSeconds(Long examRecordDataId) {
|
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
|
+ if (null == examRecordData) {
|
|
|
|
+ throw new StatusException("100001", "找不到相关考试记录");
|
|
|
|
+ }
|
|
|
|
+ Long examId = examRecordData.getExamId();
|
|
|
|
+ Long studentId = examRecordData.getStudentId();
|
|
|
|
+ Long examStageId = examRecordData.getExamStageId();
|
|
|
|
+
|
|
|
|
+ ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId, examStageId);
|
|
|
|
+ //如果未启用特殊设置,直接返回考试设置的默认考试时长
|
|
|
|
+ if (!cachedExam.getSpecialSettingsEnabled()) {
|
|
|
|
+ return cachedExam.getDuration() * 60 * 1000;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (cachedExam.getSpecialSettingsType()) {
|
|
|
|
+ case ORG_BASED:
|
|
|
|
+ return cachedExam.getDuration() * 60 * 1000;
|
|
|
|
+ case STAGE_BASED:
|
|
|
|
+ //原始的(即考试中定义的)考试时长
|
|
|
|
+ long originalExamTotalSeconds = cachedExam.getDuration() * 60 * 1000;
|
|
|
|
+
|
|
|
|
+ if (null != examStageId) {
|
|
|
|
+ ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
|
|
|
|
+ if (!examStage.getSpecialSetting()) {
|
|
|
|
+ return originalExamTotalSeconds;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (SubmitType.NORMAL.name() == examStage.getSubmitType()) {
|
|
|
|
+ return originalExamTotalSeconds;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //如果是定点交卷需要特殊处理,否则直接返回考试中定义的考试时长
|
|
|
|
+ if (examStage.getSpecialSetting() && SubmitType.TIMING_END.name() == examStage.getSubmitType()) {
|
|
|
|
+ //定点交卷时间 = 开始进入考试的起始值 + 定点交卷时长
|
|
|
|
+ Date fixedSubmitTime = DateUtils.addMinutes(examStage.getStartTime(), examStage.getSubmitDuration());
|
|
|
|
+ //实际开始答题时间
|
|
|
|
+ Date startAnswerTime = examRecordData.getStartTime();
|
|
|
|
+ //理论上考试时长 = (定点交卷时间 - 实际开始答题时间)
|
|
|
|
+ long tempTotalSeconds = fixedSubmitTime.getTime() - startAnswerTime.getTime();
|
|
|
|
+
|
|
|
|
+ return tempTotalSeconds <= originalExamTotalSeconds
|
|
|
|
+ ? tempTotalSeconds
|
|
|
|
+ : originalExamTotalSeconds;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return originalExamTotalSeconds;
|
|
|
|
+ default:
|
|
|
|
+ throw new StatusException("100002",
|
|
|
|
+ String.format("系统暂不支持的特殊设置:%s", cachedExam.getSpecialSettingsType().name()));
|
|
}
|
|
}
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1250,7 +1340,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
StartExamInfo startExamInfo = new StartExamInfo();
|
|
StartExamInfo startExamInfo = new StartExamInfo();
|
|
startExamInfo.setExamRecordDataId(examRecordDataId);
|
|
startExamInfo.setExamRecordDataId(examRecordDataId);
|
|
startExamInfo.setCourseName(courseBean.getName());
|
|
startExamInfo.setCourseName(courseBean.getName());
|
|
- startExamInfo.setDuration(examBean.getDuration());
|
|
|
|
|
|
+// startExamInfo.setDuration(examBean.getDuration());
|
|
startExamInfo.setFaceVerifyMinute(getFaceVerifyMinute(examingSession.getRootOrgId(), examBean.getId(),
|
|
startExamInfo.setFaceVerifyMinute(getFaceVerifyMinute(examingSession.getRootOrgId(), examBean.getId(),
|
|
examingSession.getOrgId(), examingSession.getStudentId()));
|
|
examingSession.getOrgId(), examingSession.getStudentId()));
|
|
return startExamInfo;
|
|
return startExamInfo;
|
|
@@ -1499,34 +1589,66 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
|
|
ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
|
|
if (examSessionInfo == null
|
|
if (examSessionInfo == null
|
|
|| examSessionInfo.getExamingStatus().equals(ExamingStatus.INFORMAL)) {
|
|
|| examSessionInfo.getExamingStatus().equals(ExamingStatus.INFORMAL)) {
|
|
- throw new StatusException("8001", "无效的会话,请离开考试");
|
|
|
|
|
|
+ throw new StatusException("101001", "无效的会话,请离开考试");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ExamRecordData examRecordDataCache =
|
|
|
|
+ examRecordDataService.getExamRecordDataCache(examSessionInfo.getExamRecordDataId());
|
|
|
|
+ //如果没有开始作答时间,则抛出异常
|
|
|
|
+ if (null == examRecordDataCache.getStartTime()) {
|
|
|
|
+ throw new StatusException("101002", "请先执行作答");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ //如果已用考试时间超过考试总时长,则不允许继续调用心跳接口
|
|
String examingHeartbeatKey = RedisKeyHelper.getBuilder().examingHeartbeatKey(examSessionInfo.getExamRecordDataId());
|
|
String examingHeartbeatKey = RedisKeyHelper.getBuilder().examingHeartbeatKey(examSessionInfo.getExamRecordDataId());
|
|
ExamingHeartbeat examingHeartbeat = redisClient.get(examingHeartbeatKey, ExamingHeartbeat.class);
|
|
ExamingHeartbeat examingHeartbeat = redisClient.get(examingHeartbeatKey, ExamingHeartbeat.class);
|
|
-
|
|
|
|
if (null != examingHeartbeat
|
|
if (null != examingHeartbeat
|
|
&& (examingHeartbeat.getCost() * 1000) >= examSessionInfo.getExamDuration()) {
|
|
&& (examingHeartbeat.getCost() * 1000) >= examSessionInfo.getExamDuration()) {
|
|
- throw new StatusException("8002", "考试会话已过期,请重新开考");
|
|
|
|
|
|
+ throw new StatusException("101003", "考试会话已过期,请重新开考");
|
|
}
|
|
}
|
|
|
|
|
|
if (null == examingHeartbeat) {
|
|
if (null == examingHeartbeat) {
|
|
examingHeartbeat = new ExamingHeartbeat();
|
|
examingHeartbeat = new ExamingHeartbeat();
|
|
- examingHeartbeat.setCost(-60L);
|
|
|
|
- examingHeartbeat.setTimes(0L);
|
|
|
|
examingHeartbeat.setExamRecordDataId(examSessionInfo.getExamRecordDataId());
|
|
examingHeartbeat.setExamRecordDataId(examSessionInfo.getExamRecordDataId());
|
|
|
|
+ examingHeartbeat.setCost(0L);
|
|
}
|
|
}
|
|
|
|
|
|
- examingHeartbeat.setTimes(examingHeartbeat.getTimes() + 1);
|
|
|
|
- examingHeartbeat.setCost(examingHeartbeat.getCost() + 60);
|
|
|
|
|
|
+ boolean isContinued =
|
|
|
|
+ (examRecordDataCache.getIsContinued() != null && examRecordDataCache.getIsContinued()) ? true : false;
|
|
|
|
+
|
|
|
|
+ Date now = new Date();
|
|
|
|
+ long usedExamSeconds = examingHeartbeat.getCost();
|
|
|
|
|
|
- redisClient.set(examingHeartbeatKey, examingHeartbeat);
|
|
|
|
|
|
+ //无断点续考,考试已用时间 = 当前时间 - 作答时间
|
|
|
|
+ if (!isContinued) {
|
|
|
|
+ usedExamSeconds = (now.getTime() - examRecordDataCache.getStartTime().getTime()) / 1000;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //如果有断点续考,考试已用时间 = 考试已用时间 + (当前时间 - 最近一次的作答时间)
|
|
|
|
+ else {
|
|
|
|
+ //获取最新的一条断点记录
|
|
|
|
+ ExamContinuedRecordEntity latestExamContinuedRecord =
|
|
|
|
+ examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examSessionInfo.getExamRecordDataId());
|
|
|
|
+ if (null == latestExamContinuedRecord) {
|
|
|
|
+ throw new StatusException("101004", "找不到断点续考记录");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (null == latestExamContinuedRecord.getStartTime()) {
|
|
|
|
+ throw new StatusException("101005", "请先执行作答");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ usedExamSeconds = usedExamSeconds + (now.getTime() - latestExamContinuedRecord.getStartTime().getTime()) / 1000;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ examingHeartbeat.setCost(usedExamSeconds);
|
|
|
|
+ redisClient.set(examingHeartbeatKey, examingHeartbeat);//更新心跳缓存
|
|
|
|
|
|
setAndSaveActiveTime(examSessionInfo.getExamRecordDataId());
|
|
setAndSaveActiveTime(examSessionInfo.getExamRecordDataId());
|
|
|
|
|
|
// 在线考生心跳打点
|
|
// 在线考生心跳打点
|
|
ReportsUtil.report(new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(),
|
|
ReportsUtil.report(new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(),
|
|
examSessionInfo.getExamId(), examSessionInfo.getExamStudentId()));
|
|
examSessionInfo.getExamId(), examSessionInfo.getExamStudentId()));
|
|
|
|
+
|
|
// 返回考试剩余时间
|
|
// 返回考试剩余时间
|
|
return examSessionInfo.getExamDuration() - (examingHeartbeat.getCost() * 1000);
|
|
return examSessionInfo.getExamDuration() - (examingHeartbeat.getCost() * 1000);
|
|
}
|
|
}
|