|
@@ -25,13 +25,12 @@ import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamRecordQuestionsR
|
|
|
import cn.com.qmth.examcloud.core.oe.student.api.response.CalcExamScoreResp;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordPaperStructResp;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordQuestionsResp;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.base.utils.Check;
|
|
|
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.*;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.dao.ExamContinuedRecordRepo;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamContinuedRecordEntity;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.report.ExamProcessRecordReport;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.service.*;
|
|
|
import cn.com.qmth.examcloud.core.oe.task.api.ExamCaptureCloudService;
|
|
@@ -57,7 +56,6 @@ import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
|
import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
|
|
|
import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
|
|
|
import cn.com.qmth.examcloud.web.exception.SequenceLockException;
|
|
|
-import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
|
|
|
import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
|
|
|
import cn.com.qmth.examcloud.web.redis.RedisClient;
|
|
|
import cn.com.qmth.examcloud.ws.api.WsCloudService;
|
|
@@ -127,6 +125,9 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
@Autowired
|
|
|
private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ExamFaceLiveVerifyService examFaceLiveVerifyService;
|
|
|
+
|
|
|
@Autowired
|
|
|
private RedisClient redisClient;
|
|
|
|
|
@@ -139,9 +140,6 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
@Autowired
|
|
|
SyncExamDataCloudService syncExamDataCloudService;
|
|
|
|
|
|
- @Autowired
|
|
|
- private ExamRecordDataRepo examRecordDataRepo;
|
|
|
-
|
|
|
@Autowired
|
|
|
private ExamContinuedRecordRepo examContinuedRecordRepo;
|
|
|
|
|
@@ -159,11 +157,18 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
@Transactional
|
|
|
@Override
|
|
|
- public StartExamInfo startExam(Long examStudentId, User user, String ip) {
|
|
|
+ public StartExamInfo startExam(Long examStudentId, Long userId, String ip) {
|
|
|
+ Check.isNull(examStudentId, "examStudentId不能为空");
|
|
|
+ Check.isNull(userId, "userId不能为空");
|
|
|
+
|
|
|
+ String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + userId;
|
|
|
+ // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
// 开考预处理
|
|
|
- prepare4Exam(examStudentId, user);
|
|
|
+ prepare4Exam(examStudentId, userId);
|
|
|
|
|
|
- Long studentId = user.getUserId();
|
|
|
+ Long studentId = userId;
|
|
|
long st = System.currentTimeMillis();
|
|
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
@@ -231,7 +236,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
|
|
|
SaveExamCaptureSyncCompareResultReq req = new SaveExamCaptureSyncCompareResultReq();
|
|
|
req.setExamRecordDataId(examRecordData.getId());
|
|
|
- req.setStudentId(user.getUserId());
|
|
|
+ req.setStudentId(userId);
|
|
|
examCaptureCloudService.saveExamCaptureSyncCompareResult(req);
|
|
|
}
|
|
|
|
|
@@ -294,21 +299,27 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
if (log.isDebugEnabled()) {
|
|
|
log.debug("10 合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
}
|
|
|
+
|
|
|
// 在线考生开考打点
|
|
|
- ReportsUtil.report(
|
|
|
- new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(), examBean.getId(), examStudentId));
|
|
|
+ ReportsUtil.report(new OnlineExamStudentReport(rootOrgId, userId, examBean.getId(), examStudentId));
|
|
|
+
|
|
|
//考试过程记录(开考)打点
|
|
|
ReportsUtil.report(
|
|
|
new ExamProcessRecordReport(examRecordData.getId(), ExamProcess.START, examRecordData.getEnterExamTime())
|
|
|
);
|
|
|
|
|
|
- StartExamInfo startExamInfo = buildStartExamInfo(examRecordData.getId(), examingSession, examBean, courseBean);
|
|
|
- return startExamInfo;
|
|
|
-
|
|
|
+ return buildStartExamInfo(examRecordData.getId(), examingSession, examBean, courseBean);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public StartAnswerInfo startAnswer(Long examRecordDataId) {
|
|
|
+ public StartAnswerInfo startAnswer(Long examRecordDataId, Long userId) {
|
|
|
+ Check.isNull(examRecordDataId, "examRecordDataId不能为空");
|
|
|
+ Check.isNull(userId, "userId不能为空");
|
|
|
+
|
|
|
+ String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + userId;
|
|
|
+ // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
Date now = new Date();
|
|
|
|
|
|
ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
@@ -506,9 +517,9 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* 开考预处理
|
|
|
*
|
|
|
* @param examStudentId
|
|
|
- * @param user
|
|
|
+ * @param userId
|
|
|
*/
|
|
|
- private void prepare4Exam(Long examStudentId, User user) {
|
|
|
+ private void prepare4Exam(Long examStudentId, Long userId) {
|
|
|
SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
|
|
|
Boolean stuClientLoginLimitBoolean = false;
|
|
|
if (stuClientLoginLimit.getHasValue()) {
|
|
@@ -525,11 +536,11 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
}
|
|
|
|
|
|
Long studentId = examStudent.getStudentId();
|
|
|
- if (!studentId.equals(user.getUserId().longValue())) {
|
|
|
+ if (!studentId.equals(userId)) {
|
|
|
throw new StatusException("008003", "考生与当前用户不吻合");
|
|
|
}
|
|
|
|
|
|
- String examingSessionKey = RedisKeyHelper.getBuilder().examingSessionKey(user.getUserId());
|
|
|
+ String examingSessionKey = RedisKeyHelper.getBuilder().examingSessionKey(userId);
|
|
|
|
|
|
ExamingSession examingSession = redisClient.get(examingSessionKey, ExamingSession.class);
|
|
|
|
|
@@ -682,6 +693,24 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
redisClient.set(examBossKey, examBoss, 60);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void manualEndExam(Long studentId, String ip) {
|
|
|
+ String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + studentId;
|
|
|
+ //系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
|
|
|
+ if (examingSession == null) {
|
|
|
+ throw new StatusException("8010", "无效的会话,请离开考试");
|
|
|
+ }
|
|
|
+
|
|
|
+ this.handInExam(examingSession.getExamRecordDataId(), HandInExamType.MANUAL, ip);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("[manualEndExam] cost " + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 交卷
|
|
|
*
|
|
@@ -1132,18 +1161,34 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @param examRecordDataId
|
|
|
*/
|
|
|
@Override
|
|
|
- public void switchScreen(Long examRecordDataId) {
|
|
|
- ExamRecordDataEntity examRecordDataEntity =
|
|
|
- GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
|
|
|
- if (null == examRecordDataEntity) {
|
|
|
+ public SwitchScreenCountInfo switchScreen(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+ if (null == examRecordData) {
|
|
|
throw new StatusException("100001", "找不到相关考试记录");
|
|
|
}
|
|
|
+
|
|
|
+ ExamingSession examSessionInfo = examingSessionService.getExamingSession(examRecordData.getStudentId());
|
|
|
+ if (examSessionInfo == null
|
|
|
+ || examSessionInfo.getExamingStatus().equals(ExamingStatus.INFORMAL)) {
|
|
|
+ throw new StatusException("101001", "无效的会话,请离开考试");
|
|
|
+ }
|
|
|
+
|
|
|
+ SwitchScreenCountInfo ret=new SwitchScreenCountInfo();
|
|
|
+ //为开启计算切屏次数的不y
|
|
|
+ if(!examSessionInfo.getRecordSwitchScreen()) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ ret.setMaxSwitchScreenCount(examSessionInfo.getMaxSwitchScreenCount());
|
|
|
|
|
|
//更新考试记录缓存
|
|
|
- ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
int switchScreenCount = null == examRecordData.getSwitchScreenCount() ? 0 : examRecordData.getSwitchScreenCount();
|
|
|
examRecordData.setSwitchScreenCount(++switchScreenCount);
|
|
|
+ ret.setSwitchScreenCount(examRecordData.getSwitchScreenCount());
|
|
|
+ if(ret.getMaxSwitchScreenCount()!=null&&ret.getSwitchScreenCount()>ret.getMaxSwitchScreenCount()) {
|
|
|
+ examRecordData.setExceedMaxSwitchScreenCount(true);
|
|
|
+ }
|
|
|
examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1272,7 +1317,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- //场次禁用,不允许考试 TODO 20200814 跟张莹确认一下,场次被禁用了是不能考试还是使用考试的设置???
|
|
|
+ //场次禁用,不允许考试
|
|
|
if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STAGE_BASED) {
|
|
|
if (null != examStageId) {
|
|
|
ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
|
|
@@ -1495,6 +1540,20 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
*/
|
|
|
public void initializeExamRecordSession(ExamingSession examSessionInfo, ExamRecordData examRecordData,
|
|
|
final ExamSettingsCacheBean examBean) {
|
|
|
+ //切屏设置
|
|
|
+ Boolean isRecordSwitchScreenCount=false;
|
|
|
+ Integer maxSwitchScreenCount=null;
|
|
|
+ OrgPropertyCacheBean ss=CacheHelper.getOrgProperty(examRecordData.getRootOrgId(), "PREVENT_CHEATING_CONFIG");
|
|
|
+ if(ss!=null&&ss.getHasValue()&&ss.getValue().contains("RECORD_SWITCH_SCREEN")) {
|
|
|
+ isRecordSwitchScreenCount=true;
|
|
|
+ ExamPropertyCacheBean sc=CacheHelper.getExamProperty(examBean.getId(), ExamProperties.MAX_SWITCH_SCREEN_COUNT.name());
|
|
|
+ if(sc!=null&&StringUtils.isNotEmpty(sc.getValue())) {
|
|
|
+ maxSwitchScreenCount=Integer.valueOf(sc.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ examSessionInfo.setMaxSwitchScreenCount(maxSwitchScreenCount);
|
|
|
+ examSessionInfo.setRecordSwitchScreen(isRecordSwitchScreenCount);
|
|
|
+
|
|
|
examSessionInfo.setExamRecordDataId(examRecordData.getId());
|
|
|
// examSessionInfo.setStartTime(examRecordData.getStartTime().getTime());//调整为在作答页面时赋值
|
|
|
examSessionInfo.setExamType(examBean.getExamType());
|
|
@@ -1521,6 +1580,29 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
log.debug("11.5 保存考试会话结束 ");
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public ExamProcessResultInfo checkExamInProgress2(Long studentId, String ip) {
|
|
|
+ String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + studentId;
|
|
|
+ // 系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
+ ExamProcessResultInfo res = new ExamProcessResultInfo();
|
|
|
+ try {
|
|
|
+ CheckExamInProgressInfo info = this.checkExamInProgress(studentId, ip);
|
|
|
+ res.setCode(Constants.COMMON_SUCCESS_CODE);
|
|
|
+ res.setData(info);
|
|
|
+ return res;
|
|
|
+ } catch (StatusException e) {
|
|
|
+ if (e.getCode().equals(Constants.EXAM_RECORD_NOT_END_STATUS_CODE)) {
|
|
|
+ res.setCode(Constants.PROCESSING_EXAM_RECORD_CODE);
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public CheckExamInProgressInfo checkExamInProgress(Long studentId, String ip) {
|
|
|
ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
|
|
@@ -1576,18 +1658,20 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
checkExamInProgressInfo.setExamType(examingRecord.getExamType());
|
|
|
|
|
|
// 断点续考时重新计算活体检测的分钟数
|
|
|
- Integer faceVerifyMinute = null;
|
|
|
+ Integer faceVerifyMinute;
|
|
|
FaceBiopsyScheme faceBiopsyScheme = FaceBiopsyHelper.getFaceBiopsyScheme(examSessionInfo.getRootOrgId());
|
|
|
-
|
|
|
- // 如果是新活体检测方案,则使用新的计算方案计算活检开始时间
|
|
|
- if (faceBiopsyScheme == FaceBiopsyScheme.NEW) {
|
|
|
+ if (FaceBiopsyScheme.FACE_CLIENT == faceBiopsyScheme) {
|
|
|
+ // C端活体检测方案
|
|
|
+ faceVerifyMinute = examFaceLiveVerifyService.calculateStartFaceVerifyMinute(examRecordDataId);
|
|
|
+ } else if (faceBiopsyScheme == FaceBiopsyScheme.FACE_MOTION) {
|
|
|
+ // Electron Client 自研活体检测方案
|
|
|
faceVerifyMinute = faceBiopsyService.calculateFaceBiopsyStartMinute(examRecordDataId);
|
|
|
- } else {// 非新活检,默认使用旧的活检计算方式
|
|
|
+ } else {
|
|
|
+ // FaceID活体检测方案
|
|
|
faceVerifyMinute = examFaceLivenessVerifyService.getFaceLivenessVerifyMinute(
|
|
|
examSessionInfo.getRootOrgId(), examSessionInfo.getOrgId(), examSessionInfo.getExamId(),
|
|
|
studentId, examSessionInfo.getExamRecordDataId(), (int) usedTime / 60);
|
|
|
}
|
|
|
-
|
|
|
checkExamInProgressInfo.setFaceVerifyMinute(faceVerifyMinute);
|
|
|
|
|
|
//考试过程记录(断点)打点
|
|
@@ -1601,6 +1685,10 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
ReportsUtil.report(new ExamProcessRecordReport(examRecordDataId, ExamProcess.CONTINUE, new Date()));
|
|
|
|
|
|
setAndSaveActiveTime(examRecordDataId, ip);
|
|
|
+
|
|
|
+ checkExamInProgressInfo.setExceedMaxSwitchScreenCount(examingRecord.getExceedMaxSwitchScreenCount());
|
|
|
+ checkExamInProgressInfo.setSwitchScreenCount(examingRecord.getSwitchScreenCount());
|
|
|
+ checkExamInProgressInfo.setMaxSwitchScreenCount(examSessionInfo.getMaxSwitchScreenCount());
|
|
|
|
|
|
return checkExamInProgressInfo;
|
|
|
}
|
|
@@ -1859,7 +1947,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
ExamRecordData examRecordData = examRecordDataService
|
|
|
.getExamRecordDataCache(examingSession.getExamRecordDataId());
|
|
|
|
|
|
- if (examRecordData != null && examRecordData.getIsExceed() != null && examRecordData.getIsExceed()) {// 超过断点最大次数的不校验冻结时间
|
|
|
+ if ((examRecordData != null && examRecordData.getIsExceed() != null && examRecordData.getIsExceed())
|
|
|
+ ||examRecordData.getExceedMaxSwitchScreenCount()) {// 超过断点最大次数或超过切屏限制的不校验冻结时间
|
|
|
return examUsedMilliSeconds;
|
|
|
}
|
|
|
long freezeTime = examingSession.getFreezeTime() * 60 * 1000;
|