|
@@ -1,19 +1,68 @@
|
|
|
package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
|
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Random;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.TimeZone;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import org.apache.commons.collections.CollectionUtils;
|
|
|
+import org.apache.commons.lang.math.RandomUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.commons.lang3.time.DateUtils;
|
|
|
+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 com.google.common.base.Splitter;
|
|
|
+
|
|
|
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.api.commons.security.bean.UserType;
|
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
+import cn.com.qmth.examcloud.commons.util.ByteUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.SHA256;
|
|
|
+import cn.com.qmth.examcloud.commons.util.StringUtil;
|
|
|
import cn.com.qmth.examcloud.commons.util.UUID;
|
|
|
-import cn.com.qmth.examcloud.commons.util.*;
|
|
|
+import cn.com.qmth.examcloud.commons.util.UrlUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.Util;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.base.bean.ExamQuestion;
|
|
|
import cn.com.qmth.examcloud.core.oe.student.base.bean.ExamRecordQuestions;
|
|
|
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.entity.ExamRecordDataEntity;
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.CheckExamInProgressInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.CheckQrCodeInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.EndExamInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.ExamRecordPaperStruct;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.GetQrCodeReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.GetUpyunSignatureReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.StartExamInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.UploadedFileAnswerInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.UpyunSignatureInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.ExamBossService;
|
|
|
+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.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;
|
|
@@ -29,7 +78,17 @@ 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.*;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
|
|
|
+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.ExamSettingsCacheBean;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.ExamStudentCacheBean;
|
|
|
+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.StudentCacheBean;
|
|
|
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
|
|
|
import cn.com.qmth.examcloud.support.enums.ExamProperties;
|
|
|
import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
|
|
|
import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
|
|
@@ -43,7 +102,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;
|
|
@@ -52,28 +110,9 @@ import cn.com.qmth.examcloud.ws.api.enums.WebSocketEventType;
|
|
|
import cn.com.qmth.examcloud.ws.api.request.SendFileAnswerMessageReq;
|
|
|
import cn.com.qmth.examcloud.ws.api.request.SendScanQrCodeMessageReq;
|
|
|
import cn.com.qmth.examcloud.ws.api.request.SendTextReq;
|
|
|
-import com.google.common.base.Splitter;
|
|
|
import main.java.com.upyun.Base64Coder;
|
|
|
import main.java.com.upyun.UpException;
|
|
|
import main.java.com.upyun.UpYunUtils;
|
|
|
-import org.apache.commons.collections.CollectionUtils;
|
|
|
-import org.apache.commons.lang.math.RandomUtils;
|
|
|
-import org.apache.commons.lang3.StringUtils;
|
|
|
-import org.apache.commons.lang3.time.DateUtils;
|
|
|
-import org.slf4j.Logger;
|
|
|
-import org.slf4j.LoggerFactory;
|
|
|
-import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.stereotype.Service;
|
|
|
-import org.springframework.transaction.annotation.Transactional;
|
|
|
-import org.springframework.web.bind.annotation.RequestParam;
|
|
|
-
|
|
|
-import java.io.UnsupportedEncodingException;
|
|
|
-import java.net.URLEncoder;
|
|
|
-import java.text.SimpleDateFormat;
|
|
|
-import java.util.*;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* @author chenken
|
|
@@ -125,16 +164,18 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
// 又拍云音频答案上传目录
|
|
|
private static final String OE_ANSWER_FILE_PATH = "oe-answer-file";
|
|
|
+
|
|
|
private static final String SESSION_TIMEOUT = "$core.basic.sessionTimeout";
|
|
|
+
|
|
|
// 又拍云签名有效时间(秒)
|
|
|
private static final Integer SIGN_TIMEOUT = 60;
|
|
|
|
|
|
@Transactional
|
|
|
@Override
|
|
|
public StartExamInfo startExam(Long examStudentId, User user) {
|
|
|
- //开考预处理
|
|
|
- prepare4Exam(examStudentId, user);
|
|
|
-
|
|
|
+ // 开考预处理
|
|
|
+ prepare4Exam(examStudentId, user);
|
|
|
+
|
|
|
Long studentId = user.getUserId();
|
|
|
long st = System.currentTimeMillis();
|
|
|
|
|
@@ -227,19 +268,20 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
|
|
|
// 创建考试作答记录
|
|
|
startTime = System.currentTimeMillis();
|
|
|
- ExamRecordQuestions examRecordQuestions = examRecordQuestionsService.createExamRecordQuestions(examRecordData.getId(),
|
|
|
- extractConfigPaper.getDefaultPaper());
|
|
|
- //记录试卷题目数量
|
|
|
- examingSession.setQuestionCount(examRecordQuestions.getExamQuestions().size());
|
|
|
+ ExamRecordQuestions examRecordQuestions = examRecordQuestionsService
|
|
|
+ .createExamRecordQuestions(examRecordData.getId(), extractConfigPaper.getDefaultPaper());
|
|
|
+ // 记录试卷题目数量
|
|
|
+ examRecordData.setQuestionCount(examRecordQuestions.getExamQuestions().size());
|
|
|
+ examRecordDataService.saveExamRecordDataCache(examRecordData.getId(), examRecordData);
|
|
|
if (log.isDebugEnabled()) {
|
|
|
log.debug("8 创建考试作答记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
}
|
|
|
|
|
|
- //保存考试次数控制信息
|
|
|
+ // 保存考试次数控制信息
|
|
|
ExamBoss eb = examBossService.getExamBoss(examingSession.getExamId());
|
|
|
- if (eb == null) {
|
|
|
- throw new StatusException("008001", "ExamBoss is not created");
|
|
|
- }
|
|
|
+ if (eb == null) {
|
|
|
+ throw new StatusException("008001", "ExamBoss is not created");
|
|
|
+ }
|
|
|
eb.setStartCount(eb.getStartCount() + 1);
|
|
|
examBossService.saveExamBoss(examId, eb);
|
|
|
|
|
@@ -260,171 +302,165 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
return startExamInfo;
|
|
|
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- *开考预处理
|
|
|
- *
|
|
|
- * @param examStudentId
|
|
|
- * @param user
|
|
|
- */
|
|
|
- private void prepare4Exam(Long examStudentId,User user ) {
|
|
|
-
|
|
|
- // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
- String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
|
|
|
- SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
-
|
|
|
- SysPropertyCacheBean stuClientLoginLimit = CacheHelper
|
|
|
- .getSysProperty("STU_CLIENT_LOGIN_LIMIT");
|
|
|
- Boolean stuClientLoginLimitBoolean = false;
|
|
|
- if (stuClientLoginLimit.getHasValue()) {
|
|
|
- stuClientLoginLimitBoolean = Boolean.valueOf(stuClientLoginLimit.getValue().toString());
|
|
|
- }
|
|
|
- if (stuClientLoginLimitBoolean) {
|
|
|
- throw new StatusException("008001", "系统维护中... ...");
|
|
|
- }
|
|
|
-
|
|
|
- ExamStudentCacheBean examStudent = CacheHelper.getExamStudent(examStudentId);
|
|
|
-
|
|
|
- if (null == examStudent) {
|
|
|
- throw new StatusException("008002", "考生不存在");
|
|
|
- }
|
|
|
-
|
|
|
- if (!examStudent.getStudentId().equals(user.getUserId().longValue())) {
|
|
|
- throw new StatusException("008003", "考生与当前用户不吻合");
|
|
|
- }
|
|
|
-
|
|
|
- String examingSessionKey = RedisKeyHelper.getBuilder().examingSessionKey(user.getUserId());
|
|
|
-
|
|
|
- ExamingSession examingSession = redisClient.get(examingSessionKey, ExamingSession.class);
|
|
|
-
|
|
|
- if (null != examingSession
|
|
|
- && examingSession.getExamingStatus().equals(ExamingStatus.FORMAL)) {
|
|
|
- throw new StatusException("008004", "已经有考试中的科目");
|
|
|
- }
|
|
|
-
|
|
|
- ExamSettingsCacheBean examSettingsCacheBean = ExamCacheTransferHelper
|
|
|
- .getCachedExam(examStudent.getExamId(), examStudent.getStudentId());
|
|
|
-
|
|
|
- StudentCacheBean studentCacheBean = CacheHelper.getStudent(examStudent.getStudentId());
|
|
|
-
|
|
|
- CourseCacheBean courseCacheBean = CacheHelper.getCourse(examStudent.getCourseId());
|
|
|
- if (!courseCacheBean.getEnable()) {
|
|
|
- throw new StatusException("100020", "课程被禁用");
|
|
|
- }
|
|
|
-
|
|
|
- // 如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
|
|
|
- ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper
|
|
|
- .getDefaultCachedExamProperty(examSettingsCacheBean.getId(),
|
|
|
- ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
|
|
|
-
|
|
|
- if (examSettingsCacheBean.getSpecialSettingsEnabled()
|
|
|
- && (limitedIfNoSpecialSettings.getHasValue()
|
|
|
- && Boolean.valueOf(limitedIfNoSpecialSettings.getValue()))) {
|
|
|
-
|
|
|
- // 学生特殊设置开启未配置,不允许考试
|
|
|
- if (examSettingsCacheBean
|
|
|
- .getSpecialSettingsType() == ExamSpecialSettingsType.STUDENT_BASED) {
|
|
|
- ExamStudentSettingsCacheBean specialSettings = CacheHelper.getExamStudentSettings(
|
|
|
- examSettingsCacheBean.getId(), examStudent.getStudentId());
|
|
|
- if (!specialSettings.getHasValue()) {
|
|
|
- throw new StatusException("100014", "考试配置未完成,不允许考试");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 机构特殊设置开启未配置,不允许考试
|
|
|
- if (examSettingsCacheBean
|
|
|
- .getSpecialSettingsType() == ExamSpecialSettingsType.ORG_BASED) {
|
|
|
- // 需求调整,所有的组织机构取学生表所关联的orgId
|
|
|
- Long orgId = CacheHelper.getStudent(examStudent.getStudentId()).getOrgId();
|
|
|
- ExamOrgSettingsCacheBean specialSettings = CacheHelper
|
|
|
- .getExamOrgSettings(examSettingsCacheBean.getId(), orgId);
|
|
|
- if (!specialSettings.getHasValue()) {
|
|
|
- throw new StatusException("100015", "考试配置未完成,不允许考试");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if (!examSettingsCacheBean.getEnable() || (examSettingsCacheBean.getExamLimit() != null
|
|
|
- && examSettingsCacheBean.getExamLimit())) {
|
|
|
- throw new StatusException("100016", "暂无考试资格,请与学校老师联系");
|
|
|
- }
|
|
|
- if (new Date().before(examSettingsCacheBean.getBeginTime())) {
|
|
|
- throw new StatusException("100017", "考试未开始");
|
|
|
- }
|
|
|
- if (examSettingsCacheBean.getEndTime().before(new Date())) {
|
|
|
- throw new StatusException("100018", "本次考试已结束");
|
|
|
- }
|
|
|
-
|
|
|
- if ((!ExamType.ONLINE.name().equals(examSettingsCacheBean.getExamType()))
|
|
|
- && (!ExamType.PRACTICE.name().equals(examSettingsCacheBean.getExamType()))) {
|
|
|
- throw new StatusException("100019", "考试类型错误");
|
|
|
- }
|
|
|
-
|
|
|
- int examTimes = examSettingsCacheBean.getExamTimes().intValue();
|
|
|
- int usedNum = examStudent.getUsedNum();
|
|
|
- int extraNum = examStudent.getExtraNum();
|
|
|
-
|
|
|
- String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examSettingsCacheBean.getId());
|
|
|
- ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
|
|
|
-
|
|
|
- int asynchronousTimes = 0;
|
|
|
- if (null != examBoss) {
|
|
|
- asynchronousTimes = examBoss.getStartCount() - examBoss.getEndCount();
|
|
|
- } else {
|
|
|
- examBoss = new ExamBoss();
|
|
|
- examBoss.setStartCount(0);
|
|
|
- examBoss.setEndCount(0);
|
|
|
- }
|
|
|
-
|
|
|
- if (examTimes + extraNum <= (usedNum + asynchronousTimes)) {
|
|
|
- throw new StatusException("100021", "无剩余考试次数");
|
|
|
- }
|
|
|
-
|
|
|
- examingSession = new ExamingSession();
|
|
|
- examingSession.setActiveTime(0L);
|
|
|
- examingSession.setCost(0L);
|
|
|
- examingSession.setCourseCode(courseCacheBean.getCode());
|
|
|
- examingSession.setCourseId(courseCacheBean.getId());
|
|
|
- examingSession.setCreationTime(new Date());
|
|
|
- examingSession.setExamDuration((long) examSettingsCacheBean.getDuration());
|
|
|
- examingSession.setExamId(examSettingsCacheBean.getId());
|
|
|
- examingSession.setExamingStatus(ExamingStatus.INFORMAL);
|
|
|
-
|
|
|
- ExamPropertyCacheBean examReconnectTime = ExamCacheTransferHelper
|
|
|
- .getDefaultCachedExamProperty(examSettingsCacheBean.getId(),
|
|
|
- ExamProperties.EXAM_RECONNECT_TIME.toString());
|
|
|
- if (null == examReconnectTime || !examReconnectTime.getHasValue()) {
|
|
|
- throw new StatusException("100022", "断点续考时间未配置");
|
|
|
- }
|
|
|
- examingSession.setExamReconnectTime(StringUtil.toInteger(examReconnectTime.getValue()));
|
|
|
-
|
|
|
- examingSession.setExamRecordDataId(null);
|
|
|
- examingSession.setExamStudentId(examStudent.getExamStudentId());
|
|
|
- examingSession.setExamType(examSettingsCacheBean.getExamType());
|
|
|
-
|
|
|
- ExamPropertyCacheBean freezeTime = ExamCacheTransferHelper.getDefaultCachedExamProperty(
|
|
|
- examSettingsCacheBean.getId(), ExamProperties.FREEZE_TIME.toString());
|
|
|
- if (null == freezeTime || !freezeTime.getHasValue()) {
|
|
|
- throw new StatusException("100023", "交卷冻结时间未配置");
|
|
|
- }
|
|
|
- examingSession.setFreezeTime(StringUtil.toInteger(freezeTime.getValue()));
|
|
|
- examingSession.setOrgId(studentCacheBean.getOrgId());
|
|
|
- examingSession.setPaperType(examStudent.getPaperType());
|
|
|
- examingSession.setRootOrgId(studentCacheBean.getRootOrgId());
|
|
|
- examingSession.setStudentId(studentCacheBean.getId());
|
|
|
-
|
|
|
- redisClient.set(examingSessionKey, examingSession, 10);
|
|
|
- redisClient.set(examBossKey, examBoss, 60);
|
|
|
-
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
+ * 开考预处理
|
|
|
+ *
|
|
|
+ * @param examStudentId
|
|
|
+ * @param user
|
|
|
+ */
|
|
|
+ private void prepare4Exam(Long examStudentId, User user) {
|
|
|
+
|
|
|
+ // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
+ String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
|
|
|
+ SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
+
|
|
|
+ SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
|
|
|
+ Boolean stuClientLoginLimitBoolean = false;
|
|
|
+ if (stuClientLoginLimit.getHasValue()) {
|
|
|
+ stuClientLoginLimitBoolean = Boolean.valueOf(stuClientLoginLimit.getValue().toString());
|
|
|
+ }
|
|
|
+ if (stuClientLoginLimitBoolean) {
|
|
|
+ throw new StatusException("008001", "系统维护中... ...");
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamStudentCacheBean examStudent = CacheHelper.getExamStudent(examStudentId);
|
|
|
+
|
|
|
+ if (null == examStudent) {
|
|
|
+ throw new StatusException("008002", "考生不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!examStudent.getStudentId().equals(user.getUserId().longValue())) {
|
|
|
+ throw new StatusException("008003", "考生与当前用户不吻合");
|
|
|
+ }
|
|
|
+
|
|
|
+ String examingSessionKey = RedisKeyHelper.getBuilder().examingSessionKey(user.getUserId());
|
|
|
+
|
|
|
+ ExamingSession examingSession = redisClient.get(examingSessionKey, ExamingSession.class);
|
|
|
+
|
|
|
+ if (null != examingSession && examingSession.getExamingStatus().equals(ExamingStatus.FORMAL)) {
|
|
|
+ throw new StatusException("008004", "已经有考试中的科目");
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamSettingsCacheBean examSettingsCacheBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(),
|
|
|
+ examStudent.getStudentId());
|
|
|
+
|
|
|
+ StudentCacheBean studentCacheBean = CacheHelper.getStudent(examStudent.getStudentId());
|
|
|
+
|
|
|
+ CourseCacheBean courseCacheBean = CacheHelper.getCourse(examStudent.getCourseId());
|
|
|
+ if (!courseCacheBean.getEnable()) {
|
|
|
+ throw new StatusException("100020", "课程被禁用");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
|
|
|
+ ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(
|
|
|
+ examSettingsCacheBean.getId(), ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
|
|
|
+
|
|
|
+ if (examSettingsCacheBean.getSpecialSettingsEnabled() && (limitedIfNoSpecialSettings.getHasValue()
|
|
|
+ && Boolean.valueOf(limitedIfNoSpecialSettings.getValue()))) {
|
|
|
+
|
|
|
+ // 学生特殊设置开启未配置,不允许考试
|
|
|
+ if (examSettingsCacheBean.getSpecialSettingsType() == ExamSpecialSettingsType.STUDENT_BASED) {
|
|
|
+ ExamStudentSettingsCacheBean specialSettings = CacheHelper
|
|
|
+ .getExamStudentSettings(examSettingsCacheBean.getId(), examStudent.getStudentId());
|
|
|
+ if (!specialSettings.getHasValue()) {
|
|
|
+ throw new StatusException("100014", "考试配置未完成,不允许考试");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 机构特殊设置开启未配置,不允许考试
|
|
|
+ if (examSettingsCacheBean.getSpecialSettingsType() == ExamSpecialSettingsType.ORG_BASED) {
|
|
|
+ // 需求调整,所有的组织机构取学生表所关联的orgId
|
|
|
+ Long orgId = CacheHelper.getStudent(examStudent.getStudentId()).getOrgId();
|
|
|
+ ExamOrgSettingsCacheBean specialSettings = CacheHelper.getExamOrgSettings(examSettingsCacheBean.getId(),
|
|
|
+ orgId);
|
|
|
+ if (!specialSettings.getHasValue()) {
|
|
|
+ throw new StatusException("100015", "考试配置未完成,不允许考试");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!examSettingsCacheBean.getEnable()
|
|
|
+ || (examSettingsCacheBean.getExamLimit() != null && examSettingsCacheBean.getExamLimit())) {
|
|
|
+ throw new StatusException("100016", "暂无考试资格,请与学校老师联系");
|
|
|
+ }
|
|
|
+ if (new Date().before(examSettingsCacheBean.getBeginTime())) {
|
|
|
+ throw new StatusException("100017", "考试未开始");
|
|
|
+ }
|
|
|
+ if (examSettingsCacheBean.getEndTime().before(new Date())) {
|
|
|
+ throw new StatusException("100018", "本次考试已结束");
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((!ExamType.ONLINE.name().equals(examSettingsCacheBean.getExamType()))
|
|
|
+ && (!ExamType.PRACTICE.name().equals(examSettingsCacheBean.getExamType()))) {
|
|
|
+ throw new StatusException("100019", "考试类型错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ int examTimes = examSettingsCacheBean.getExamTimes().intValue();
|
|
|
+ int usedNum = examStudent.getUsedNum();
|
|
|
+ int extraNum = examStudent.getExtraNum();
|
|
|
+
|
|
|
+ String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examSettingsCacheBean.getId());
|
|
|
+ ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
|
|
|
+
|
|
|
+ int asynchronousTimes = 0;
|
|
|
+ if (null != examBoss) {
|
|
|
+ asynchronousTimes = examBoss.getStartCount() - examBoss.getEndCount();
|
|
|
+ } else {
|
|
|
+ examBoss = new ExamBoss();
|
|
|
+ examBoss.setStartCount(0);
|
|
|
+ examBoss.setEndCount(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (examTimes + extraNum <= (usedNum + asynchronousTimes)) {
|
|
|
+ throw new StatusException("100021", "无剩余考试次数");
|
|
|
+ }
|
|
|
+
|
|
|
+ examingSession = new ExamingSession();
|
|
|
+ examingSession.setActiveTime(0L);
|
|
|
+ examingSession.setCost(0L);
|
|
|
+ examingSession.setCourseCode(courseCacheBean.getCode());
|
|
|
+ examingSession.setCourseId(courseCacheBean.getId());
|
|
|
+ examingSession.setCreationTime(new Date());
|
|
|
+ examingSession.setExamDuration((long) examSettingsCacheBean.getDuration());
|
|
|
+ examingSession.setExamId(examSettingsCacheBean.getId());
|
|
|
+ examingSession.setExamingStatus(ExamingStatus.INFORMAL);
|
|
|
+
|
|
|
+ ExamPropertyCacheBean examReconnectTime = ExamCacheTransferHelper.getDefaultCachedExamProperty(
|
|
|
+ examSettingsCacheBean.getId(), ExamProperties.EXAM_RECONNECT_TIME.toString());
|
|
|
+ if (null == examReconnectTime || !examReconnectTime.getHasValue()) {
|
|
|
+ throw new StatusException("100022", "断点续考时间未配置");
|
|
|
+ }
|
|
|
+ examingSession.setExamReconnectTime(StringUtil.toInteger(examReconnectTime.getValue()));
|
|
|
+
|
|
|
+ examingSession.setExamRecordDataId(null);
|
|
|
+ examingSession.setExamStudentId(examStudent.getExamStudentId());
|
|
|
+ examingSession.setExamType(examSettingsCacheBean.getExamType());
|
|
|
+
|
|
|
+ ExamPropertyCacheBean freezeTime = ExamCacheTransferHelper
|
|
|
+ .getDefaultCachedExamProperty(examSettingsCacheBean.getId(), ExamProperties.FREEZE_TIME.toString());
|
|
|
+ if (null == freezeTime || !freezeTime.getHasValue()) {
|
|
|
+ throw new StatusException("100023", "交卷冻结时间未配置");
|
|
|
+ }
|
|
|
+ examingSession.setFreezeTime(StringUtil.toInteger(freezeTime.getValue()));
|
|
|
+ examingSession.setOrgId(studentCacheBean.getOrgId());
|
|
|
+ examingSession.setPaperType(examStudent.getPaperType());
|
|
|
+ examingSession.setRootOrgId(studentCacheBean.getRootOrgId());
|
|
|
+ examingSession.setStudentId(studentCacheBean.getId());
|
|
|
+
|
|
|
+ redisClient.set(examingSessionKey, examingSession, 10);
|
|
|
+ redisClient.set(examBossKey, examBoss, 60);
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 交卷
|
|
|
*
|
|
|
- * @param examRecordDataId 考试记录id
|
|
|
- * @param handInExamType 交卷类型
|
|
|
+ * @param examRecordDataId
|
|
|
+ * 考试记录id
|
|
|
+ * @param handInExamType
|
|
|
+ * 交卷类型
|
|
|
*/
|
|
|
@Transactional
|
|
|
@Override
|
|
@@ -549,8 +585,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
res.setCourseId(courseBean.getId());
|
|
|
res.setCourseName(courseBean.getName());
|
|
|
|
|
|
- ExamRecordQuestions examRecordQuestions = examRecordQuestionsService.getExamRecordQuestions(
|
|
|
- Long.valueOf(examRecordDataId), examSessionInfo.getQuestionCount());
|
|
|
+ ExamRecordQuestions examRecordQuestions = examRecordQuestionsService
|
|
|
+ .getExamRecordQuestions(Long.valueOf(examRecordDataId));
|
|
|
|
|
|
List<ExamQuestion> examQuestionList = examRecordQuestions.getExamQuestions();
|
|
|
|
|
@@ -568,7 +604,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
res.setQuestionMainNumber(eqe.getMainNumber());
|
|
|
res.setSubNumber(getSubNumber(examRecordQuestions, Integer.valueOf(order)));
|
|
|
try {
|
|
|
- ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(Long.valueOf(examRecordDataId));
|
|
|
+ ExamRecordData examRecordData = examRecordDataService
|
|
|
+ .getExamRecordDataCache(Long.valueOf(examRecordDataId));
|
|
|
this.sendScanQrCodeToWebSocket(clientId, Long.valueOf(examRecordDataId), Integer.valueOf(order),
|
|
|
Long.valueOf(userId), examRecordData.getRootOrgId());
|
|
|
} catch (Exception e) {
|
|
@@ -577,20 +614,23 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* 发送消息到websocket
|
|
|
*
|
|
|
- * @param examRecordDataId 考试记录id
|
|
|
- * @param order 题序号
|
|
|
- * @param fileUrl 文件路径
|
|
|
- * @param transferFileType 传输文件类型
|
|
|
+ * @param examRecordDataId
|
|
|
+ * 考试记录id
|
|
|
+ * @param order
|
|
|
+ * 题序号
|
|
|
+ * @param fileUrl
|
|
|
+ * 文件路径
|
|
|
+ * @param transferFileType
|
|
|
+ * 传输文件类型
|
|
|
* @param userId
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
@Override
|
|
|
public void sendFileAnswerToWebSocket(Long examRecordDataId, Integer order, String fileUrl, String transferFileType,
|
|
|
- Long userId, Long rootOrgId) throws Exception {
|
|
|
+ Long userId, Long rootOrgId) throws Exception {
|
|
|
Map<String, Object> data = new HashMap<String, Object>();
|
|
|
data.put("examRecordDataId", examRecordDataId);
|
|
|
data.put("order", order);
|
|
@@ -630,8 +670,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
@Override
|
|
|
- public void sendScanQrCodeToWebSocket(String clientId, Long examRecordDataId, Integer order,
|
|
|
- Long userId, Long rootOrgId) throws Exception {
|
|
|
+ public void sendScanQrCodeToWebSocket(String clientId, Long examRecordDataId, Integer order, Long userId,
|
|
|
+ Long rootOrgId) throws Exception {
|
|
|
Map<String, Object> data = new HashMap<String, Object>();
|
|
|
data.put("examRecordDataId", examRecordDataId);
|
|
|
data.put("order", order);
|
|
@@ -759,11 +799,16 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @param bucketName //不能为空
|
|
|
- * @param expiration //不能为空
|
|
|
- * @param filePath //不能为空
|
|
|
- * @param date 为空时,以又怕云时间和expiration比较,不为空时以此date和expiration比较
|
|
|
- * @param md5 //可以为空
|
|
|
+ * @param bucketName
|
|
|
+ * //不能为空
|
|
|
+ * @param expiration
|
|
|
+ * //不能为空
|
|
|
+ * @param filePath
|
|
|
+ * //不能为空
|
|
|
+ * @param date
|
|
|
+ * 为空时,以又怕云时间和expiration比较,不为空时以此date和expiration比较
|
|
|
+ * @param md5
|
|
|
+ * //可以为空
|
|
|
* @return
|
|
|
*/
|
|
|
private String policy(String bucketName, Long expiration, String filePath, Date date, String md5) {
|
|
@@ -805,7 +850,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @throws UpException
|
|
|
*/
|
|
|
private String sign(String method, String date, String bucketName, String policy, String userName, String password,
|
|
|
- String md5) throws UpException {
|
|
|
+ String md5) throws UpException {
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
String sp = "&";
|
|
@@ -953,8 +998,10 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
/**
|
|
|
* 获取试卷结构 小题乱序、选项乱序
|
|
|
*
|
|
|
- * @param extractConfig 调卷规则对象
|
|
|
- * @param paperStruct 试卷结构对象
|
|
|
+ * @param extractConfig
|
|
|
+ * 调卷规则对象
|
|
|
+ * @param paperStruct
|
|
|
+ * 试卷结构对象
|
|
|
*/
|
|
|
private void reorderPaperStruct(ExtractConfigCacheBean extractConfig, ExtractConfigPaperCacheBean paperStruct) {
|
|
|
// 小题乱序
|
|
@@ -1038,8 +1085,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession, ExamSettingsCacheBean examBean,
|
|
|
- CourseCacheBean courseBean) {
|
|
|
+ private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession,
|
|
|
+ ExamSettingsCacheBean examBean, CourseCacheBean courseBean) {
|
|
|
StartExamInfo startExamInfo = new StartExamInfo();
|
|
|
startExamInfo.setExamRecordDataId(examRecordDataId);
|
|
|
startExamInfo.setCourseName(courseBean.getName());
|
|
@@ -1086,7 +1133,7 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
* @param examBean
|
|
|
*/
|
|
|
public void initializeExamRecordSession(ExamingSession examSessionInfo, ExamRecordData examRecordData,
|
|
|
- final ExamSettingsCacheBean examBean) {
|
|
|
+ final ExamSettingsCacheBean examBean) {
|
|
|
examSessionInfo.setExamRecordDataId(examRecordData.getId());
|
|
|
examSessionInfo.setStartTime(examRecordData.getStartTime().getTime());
|
|
|
examSessionInfo.setExamType(examBean.getExamType());
|
|
@@ -1193,7 +1240,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
/**
|
|
|
* 如果有序列化锁,则延迟交卷
|
|
|
*
|
|
|
- * @param examRecordDataId 考试记录id
|
|
|
+ * @param examRecordDataId
|
|
|
+ * 考试记录id
|
|
|
* @return
|
|
|
*/
|
|
|
private void delayHandInExamIfLocked(Long examRecordDataId) {
|
|
@@ -1239,7 +1287,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
/**
|
|
|
* 考试心跳每分钟调用一次
|
|
|
*
|
|
|
- * @param user 学生
|
|
|
+ * @param user
|
|
|
+ * 学生
|
|
|
*/
|
|
|
@Override
|
|
|
public long examHeartbeat(User user) {
|
|
@@ -1268,7 +1317,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
/**
|
|
|
* 计算考试时长 校验是否达到冻结时间
|
|
|
*
|
|
|
- * @param studentId 学生id
|
|
|
+ * @param studentId
|
|
|
+ * 学生id
|
|
|
* @return
|
|
|
*/
|
|
|
private Long checkAndComputeExamDuration(Long studentId) {
|
|
@@ -1281,7 +1331,8 @@ public class ExamControlServiceImpl implements ExamControlService {
|
|
|
Long examUsedMilliSeconds = examingSession.getCost() * 1000;
|
|
|
// 如果没有超过冻结时间,抛出异常
|
|
|
if (examingSession.getExamType().equals(ExamType.ONLINE.name())) {
|
|
|
- ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examingSession.getExamRecordDataId());
|
|
|
+ ExamRecordData examRecordData = examRecordDataService
|
|
|
+ .getExamRecordDataCache(examingSession.getExamRecordDataId());
|
|
|
|
|
|
if (examRecordData != null && examRecordData.getIsExceed() != null && examRecordData.getIsExceed()) {// 超过断点最大次数的不校验冻结时间
|
|
|
return examUsedMilliSeconds;
|