|
@@ -0,0 +1,1571 @@
|
|
|
+package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
|
+
|
|
|
+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.ByteUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.SHA256;
|
|
|
+import cn.com.qmth.examcloud.commons.util.UrlUtil;
|
|
|
+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.common.base.Constants;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.base.utils.CommonUtil;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.base.utils.QuestionTypeUtil;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.entity.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.enums.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.repository.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.service.ExamScoreObtainQueueService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.service.ExamScorePushQueueService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.service.GainBaseDataService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.face.api.ExamCaptureQueueCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.websocket.api.FileAnswerWebsocketCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.websocket.api.enums.WebSocketEventType;
|
|
|
+import cn.com.qmth.examcloud.core.oe.websocket.api.request.SendFileAnswerMessageReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.websocket.api.request.SendScanQrCodeMessageReq;
|
|
|
+import cn.com.qmth.examcloud.core.questions.api.ExtractConfigCloudService;
|
|
|
+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;
|
|
|
+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.DefaultQuestionUnitWrapper;
|
|
|
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
|
|
|
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
|
|
|
+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.web.bootstrap.PropertyHolder;
|
|
|
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
|
|
|
+import cn.com.qmth.examcloud.web.redis.RedisClient;
|
|
|
+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.lang3.StringUtils;
|
|
|
+import org.apache.commons.lang3.time.DateUtils;
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
|
+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 javax.validation.Valid;
|
|
|
+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
|
|
|
+ * @date 2018年8月13日 下午2:09:08
|
|
|
+ * @company QMTH
|
|
|
+ * @description 在线考试控制服务实现
|
|
|
+ */
|
|
|
+@Service("examControlService")
|
|
|
+public class ExamControlServiceImpl implements ExamControlService {
|
|
|
+ private static final String SESSION_TIMEOUT = "$core.basic.sessionTimeout";
|
|
|
+ // 又拍云签名有效时间(秒)
|
|
|
+ private static final Integer SIGN_TIMEOUT = 60;
|
|
|
+ private static final String SEPARATOR = "/";
|
|
|
+ private static final String UNDERLINE = "_";
|
|
|
+ // 又拍云音频答案上传目录
|
|
|
+ private static final String OE_ANSWER_FILE_PATH = "oe-answer-file";
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ExamControlServiceImpl.class);
|
|
|
+ private static final Log cleanExamRecordTaskLog = LogFactory.getLog("CLEAN_EXAM_RECORD_TASK_LOGGER");
|
|
|
+ // 发送题目答案序号集合
|
|
|
+// private static ConcurrentHashMap<String,Integer> sendQuestionAnswerSequenceMap=new ConcurrentHashMap<String,Integer>();
|
|
|
+ @Autowired
|
|
|
+ private ExamStudentService examStudentService;
|
|
|
+ @Autowired
|
|
|
+ private ExamCloudService examCloudService;
|
|
|
+ @Autowired
|
|
|
+ private ExamSessionInfoService examSessionInfoService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordDataService examRecordDataService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordQuestionsService examRecordQuestionsService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordService examRecordService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamScoreService examScoreService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureQueueCloudService examCaptureQueueCloudService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordForMarkingService examRecordForMarkingService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GainBaseDataService gainBaseDataService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamStudentRepo examStudentRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamScoreRepo examScoreRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordDataRepo examRecordDataRepo;
|
|
|
+ @Autowired
|
|
|
+ private ExamingRecordRepo examingRecordRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordPaperStructRepo examRecordPaperStructRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamFaceLivenessVerifyRepo examFaceLivenessVerifyRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisClient redisClient;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamScorePushQueueService examScorePushQueueService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamScoreObtainQueueService examScoreObtainQueueService;
|
|
|
+ @Autowired
|
|
|
+ private ExamFileAnswerTempRepo examFileAnswerTempRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FileAnswerWebsocketCloudService fileAnswerWebsocketCloudService;
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureQueueRepo examCaptureQueueRepo;
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordRepo examRecordRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExtractConfigCloudService extractConfigCloudService;
|
|
|
+ @Value("${audio.app.url}")
|
|
|
+ private String audioAppUrl;
|
|
|
+ @Value("${$upyun.site.1.bucketName}")
|
|
|
+ private String bucketName;
|
|
|
+
|
|
|
+ @Value("${$upyun.site.1.userName}")
|
|
|
+ private String userName;
|
|
|
+
|
|
|
+ @Value("${$upyun.site.1.password}")
|
|
|
+ private String password;
|
|
|
+ @Value("https://v0.api.upyun.com")
|
|
|
+ private String bucketUrl;
|
|
|
+ @Value("${$upyun.site.1.domain}")
|
|
|
+ private String upyunFileUrl;
|
|
|
+
|
|
|
+ private Object lock = new Object();
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public StartExamInfo startExam(Long examStudentId, User user) {
|
|
|
+ long st = System.currentTimeMillis();
|
|
|
+ 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("OE-001505", "系统维护中... ...");
|
|
|
+ }
|
|
|
+
|
|
|
+// try {
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ // 获取考生信息
|
|
|
+ ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examStudentId);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("1 获取考生信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ if (examStudentEntity == null) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-startExam-exception", "考生不存在");
|
|
|
+ }
|
|
|
+ if (examStudentEntity.getStudentId().longValue() != user.getUserId().longValue()) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-startExam-exception", "考生与当前用户不吻合");
|
|
|
+ }
|
|
|
+ // 检查redis session
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService
|
|
|
+ .getExamSessionInfo(examStudentEntity.getStudentId());
|
|
|
+ if (examSessionInfo != null) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-startExam-exception", "已经有考试中的科目");
|
|
|
+ }
|
|
|
+ // 检查并获取考试信息
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ ExamBean examBean = checkExam(examStudentEntity);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("2 检查并获取考试信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查并获取课程信息
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ CourseBean courseBean = checkCourse(examStudentEntity);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("3 检查并获取课程信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取题库试卷结构(由于存在随机抽卷,所以不能缓存 )
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ //获取题库调卷规则
|
|
|
+ ExtractConfigCacheBean extractConfig = CacheHelper.getExtractConfig(
|
|
|
+ examStudentEntity.getExamId(), courseBean.getCode());
|
|
|
+ //随机生成试卷
|
|
|
+ Map<String, String> paperTypeMaps = getExamPaperByProbability(extractConfig.getDetails());
|
|
|
+ if (paperTypeMaps.isEmpty()) {
|
|
|
+ throw new StatusException("100001", "生成试卷失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ String paperId = paperTypeMaps.get(examStudentEntity.getPaperType());
|
|
|
+ if (StringUtils.isEmpty(paperId)) {
|
|
|
+ throw new StatusException("100002", "获取试卷失败");
|
|
|
+ }
|
|
|
+ //生成试卷结构
|
|
|
+ ExtractConfigPaperCacheBean extractConfigPaper = CacheHelper.getExtractConfigPaper(examStudentEntity.getExamId(),
|
|
|
+ courseBean.getCode(), examStudentEntity.getPaperType(), paperId);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("4 获取题库试卷结构耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 小题乱序,选项乱序
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ reorderPaperStruct(extractConfig, extractConfigPaper);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("5 小题乱序耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存考试试卷结构
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ ExamRecordPaperStructEntity examRecordPaperStruct = new ExamRecordPaperStructEntity();
|
|
|
+ examRecordPaperStruct.setDefaultPaper(extractConfigPaper.getDefaultPaper());
|
|
|
+ examRecordPaperStruct = examRecordPaperStructRepo.save(examRecordPaperStruct);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("6 保存考试试卷结构耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新考生信息
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ examStudentEntity=examStudentService.updateExamStudentByStartExam(examStudentEntity, examBean);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("7 更新考生信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成考试记录
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ ExamRecordEntity examRecord = examRecordService.createExamRecord(examStudentEntity, examBean, courseBean,
|
|
|
+ paperId, examRecordPaperStruct.getId());
|
|
|
+ ExamRecordDataEntity examRecordData = examRecordDataService.createExamRecordData(examRecord,
|
|
|
+ examStudentEntity, examBean, extractConfigPaper.getDefaultPaper().getFullyObjective());
|
|
|
+ //生成进行中的考试记录
|
|
|
+ examRecordDataService.createExamingRecord(examRecord.getId(), examRecordData.getId(),
|
|
|
+ examRecord.getStudentId(), examRecord.getExamType());
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("8 生成考试记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建考试作答记录
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ ExamRecordQuestionsEntity examRecordQuestions = examRecordQuestionsService.createExamRecordQuestions(
|
|
|
+ examRecordData.getId(), extractConfigPaper.getDefaultPaper());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("9 创建考试作答记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ //更新考试记录(冗余考试记录id)
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ examRecordData.setExamRecordQuestionsId(examRecordQuestions.getId());
|
|
|
+ examRecordDataRepo.updateExamRecordDataQuestionIdById(examRecordQuestions.getId(), examRecordData.getId());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("10 更新考试记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建考试会话
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ initializeExamRecordSession(examStudentEntity, examRecordData, examBean);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("11 创建考试会话耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("12 合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
+ }
|
|
|
+ return buildStartExamInfo(examRecordData.getId(), examStudentEntity, examBean, courseBean);
|
|
|
+// } finally {
|
|
|
+//
|
|
|
+// }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 每个试卷类型取出一套试卷
|
|
|
+ * {A:paperId,B:paperId} A是试卷类型,paperId是A类型下选定的试卷ID
|
|
|
+ */
|
|
|
+ private Map<String, String> getExamPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
|
|
|
+ if (CollectionUtils.isEmpty(examPapers)) {
|
|
|
+ throw new StatusException("500", "可供抽取的试卷集合为空!");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, List<ExtractConfigDetailCacheBean>> examPaperMaps = new HashMap<>();
|
|
|
+ for (ExtractConfigDetailCacheBean examPaper : examPapers) {
|
|
|
+ if (examPaperMaps.containsKey(examPaper.getGroupCode())) {
|
|
|
+ examPaperMaps.get(examPaper.getGroupCode()).add(examPaper);
|
|
|
+ } else {
|
|
|
+ List<ExtractConfigDetailCacheBean> list = new ArrayList<>();
|
|
|
+ list.add(examPaper);
|
|
|
+ examPaperMaps.put(examPaper.getGroupCode(), list);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, String> paperTypeMaps = new HashMap<>();
|
|
|
+ for (Map.Entry<String, List<ExtractConfigDetailCacheBean>> entry : examPaperMaps.entrySet()) {
|
|
|
+ List<ExtractConfigDetailCacheBean> list = examPaperMaps.get(entry.getKey());
|
|
|
+
|
|
|
+ String paperId = this.getPaperByProbability(list);
|
|
|
+ if (StringUtils.isEmpty(paperId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ paperTypeMaps.put(entry.getKey(), paperId);
|
|
|
+ }
|
|
|
+
|
|
|
+ return paperTypeMaps;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据设定几率取出一套试卷
|
|
|
+ */
|
|
|
+ private String getPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
|
|
|
+ int sum = 0;
|
|
|
+ for (ExtractConfigDetailCacheBean examPaper : examPapers) {
|
|
|
+ sum += examPaper.getWeight();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从1开始
|
|
|
+ int r = new Random().nextInt(sum) + 1;
|
|
|
+ for (ExtractConfigDetailCacheBean examPaper : examPapers) {
|
|
|
+ r -= examPaper.getWeight();
|
|
|
+ if (r <= 0) {
|
|
|
+ return examPaper.getPaperId();// 选中
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamStudentEntity examStudentEntity,
|
|
|
+ ExamBean examBean, CourseBean courseBean) {
|
|
|
+ StartExamInfo startExamInfo = new StartExamInfo();
|
|
|
+ startExamInfo.setExamRecordDataId(examRecordDataId);
|
|
|
+ startExamInfo.setStudentCode(examStudentEntity.getStudentCode());
|
|
|
+ startExamInfo.setStudentName(examStudentEntity.getStudentName());
|
|
|
+ startExamInfo.setCourseCode(examStudentEntity.getCourseCode());
|
|
|
+ startExamInfo.setCourseName(courseBean.getName());
|
|
|
+ startExamInfo.setDuration(examBean.getDuration());
|
|
|
+ startExamInfo.setFaceVerifyMinute(getFaceVerifyMinute(examBean.getId(), examStudentEntity.getOrgId()));
|
|
|
+ return startExamInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 确定活体检测开始分钟数
|
|
|
+ *
|
|
|
+ * @param examId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer getFaceVerifyMinute(Long examId, Long orgId) {
|
|
|
+ String isFaceVerifyStr = CacheHelper.getExamOrgProperty(
|
|
|
+ examId, orgId, ExamProperties.IS_FACE_VERIFY.name()).getValue();
|
|
|
+ // 如果开启了活体检测
|
|
|
+ if (Constants.isTrue.equals(isFaceVerifyStr)) {
|
|
|
+ // 开始分钟数
|
|
|
+ String startMinuteStr = CacheHelper.getExamOrgProperty(
|
|
|
+ examId, orgId, ExamProperties.FACE_VERIFY_START_MINUTE.name()).getValue();
|
|
|
+ if (CommonUtil.isBlank(startMinuteStr)) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-getFaceVerifyMinute-001",
|
|
|
+ ExamProperties.FACE_VERIFY_START_MINUTE.getDesc() + "未设置");
|
|
|
+ }
|
|
|
+ Integer faceVerifyStartMinute = Integer.valueOf(startMinuteStr);
|
|
|
+
|
|
|
+ // 结束分钟数
|
|
|
+ String endMinuteStr = CacheHelper.getExamOrgProperty(
|
|
|
+ examId, orgId, ExamProperties.FACE_VERIFY_END_MINUTE.name()).getValue();
|
|
|
+ if (CommonUtil.isBlank(endMinuteStr)) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-getFaceVerifyMinute-002",
|
|
|
+ ExamProperties.FACE_VERIFY_END_MINUTE.getDesc() + "未设置");
|
|
|
+ }
|
|
|
+ Integer faceVerifyEndMinute = Integer.valueOf(endMinuteStr);
|
|
|
+ return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private CourseBean checkCourse(ExamStudentEntity examStudentEntity) {
|
|
|
+ CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
|
|
|
+ if (!courseBean.getEnable()) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkCourse-exception", "该课程已被禁用");
|
|
|
+ }
|
|
|
+ return courseBean;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查并返回考试 开考条件 1.enable为true 2.开始时间和结束时间判断 3.examLimit为null或false 4.剩余考试次数>0
|
|
|
+ *
|
|
|
+ * @param examStudentEntity
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private ExamBean checkExam(ExamStudentEntity examStudentEntity) {
|
|
|
+
|
|
|
+// examStudentEntity.getOrgId());
|
|
|
+ ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudentEntity.getExamId(),
|
|
|
+ examStudentEntity.getOrgId());
|
|
|
+ if (!examBean.getEnable() || (examBean.getExamLimit() != null && examBean.getExamLimit())) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkExam-exception-01", "暂无考试资格,请与学校老师联系");
|
|
|
+ }
|
|
|
+ if (new Date().before(examBean.getBeginTime())) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkExam-exception-02", "考试未开始");
|
|
|
+ }
|
|
|
+ if (examBean.getEndTime().before(new Date())) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkExam-exception-03", "本次考试已结束");
|
|
|
+ }
|
|
|
+ if (ExamType.ONLINE.name().equals(examBean.getExamType())
|
|
|
+ || ExamType.PRACTICE.name().equals(examBean.getExamType())) {
|
|
|
+ if (examBean.getExamTimes() == null
|
|
|
+ || examStudentService.countExamTimes(examStudentEntity, examBean) <= 0) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkExam-exception-04", "无剩余考试次数可用");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return examBean;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建考试会话
|
|
|
+ *
|
|
|
+ * @param examRecordData
|
|
|
+ * @param examBean
|
|
|
+ */
|
|
|
+ public void initializeExamRecordSession(final ExamStudentEntity examStudent,
|
|
|
+ final ExamRecordDataEntity examRecordData, final ExamBean examBean) {
|
|
|
+ ExamSessionInfo examSessionInfo = new ExamSessionInfo();
|
|
|
+ examSessionInfo.setExamRecordId(examRecordData.getExamRecordId());
|
|
|
+ examSessionInfo.setExamRecordDataId(examRecordData.getId());
|
|
|
+ examSessionInfo.setExamStudentId(examStudent.getExamStudentId());
|
|
|
+ examSessionInfo.setStartTime(examRecordData.getStartTime().getTime());
|
|
|
+ long examDuration = examBean.getDuration() * 60 * 1000;
|
|
|
+ examSessionInfo.setExamDuration(examDuration);
|
|
|
+ examSessionInfo.setHeartbeat(-1);
|
|
|
+ examSessionInfo.setExamType(examBean.getExamType());
|
|
|
+ examSessionInfo.setExamId(examBean.getId());
|
|
|
+ examSessionInfo.setCourseCode(examStudent.getCourseCode());
|
|
|
+ examSessionInfo.setPaperType(examStudent.getPaperType());
|
|
|
+ // EXAM_RECONNECT_TIME:断点续考时间
|
|
|
+ String examReconnectTimeStr = CacheHelper.getExamOrgProperty(examBean.getId(), examStudent.getOrgId(),
|
|
|
+ ExamProperties.EXAM_RECONNECT_TIME.name()).getValue();
|
|
|
+ log.debug("11.2 断点时间:" + examReconnectTimeStr);
|
|
|
+ if (CommonUtil.isBlank(examReconnectTimeStr)) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-initializeExamRecordSession-001",
|
|
|
+ ExamProperties.EXAM_RECONNECT_TIME.getDesc() + "未设置");
|
|
|
+ }
|
|
|
+ examSessionInfo.setExamReconnectTime(Integer.valueOf(examReconnectTimeStr));
|
|
|
+ // FREEZE_TIME:冻结时间
|
|
|
+ String freezeTimeStr = CacheHelper.getExamOrgProperty(examBean.getId(), examStudent.getOrgId(),
|
|
|
+ ExamProperties.FREEZE_TIME.name()).getValue();
|
|
|
+ log.debug("11.3 冻结时间:" + freezeTimeStr);
|
|
|
+ if (CommonUtil.isBlank(freezeTimeStr)) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-initializeExamRecordSession-002",
|
|
|
+ ExamProperties.FREEZE_TIME.getDesc() + "未设置");
|
|
|
+ }
|
|
|
+ examSessionInfo.setFreezeTime(Integer.valueOf(freezeTimeStr));
|
|
|
+
|
|
|
+ log.debug("11.4 开始保存考试会话...studentId=" + examStudent.getStudentId() + ",timeOut=" + examSessionInfo.getExamReconnectTime() * 60);
|
|
|
+ // 过期时间=断点续考时间
|
|
|
+ examSessionInfoService.saveExamSessionInfo(examStudent.getStudentId(), examSessionInfo,
|
|
|
+ examSessionInfo.getExamReconnectTime() * 60);
|
|
|
+ log.debug("11.5 保存考试会话结束 ");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取试卷结构 小题乱序、选项乱序
|
|
|
+ *
|
|
|
+ * @param extractConfig 调卷规则对象
|
|
|
+ * @param paperStruct 试卷结构对象
|
|
|
+ */
|
|
|
+ private void reorderPaperStruct(ExtractConfigCacheBean extractConfig, ExtractConfigPaperCacheBean paperStruct) {
|
|
|
+ // 小题乱序
|
|
|
+ if (extractConfig.getSortQuestionOrder() != null && extractConfig.getSortQuestionOrder()) {
|
|
|
+ reorderQuestion(paperStruct.getDefaultPaper());
|
|
|
+ }
|
|
|
+ // 选项乱序
|
|
|
+ if (extractConfig.getSortOptionOrder() != null && extractConfig.getSortOptionOrder()) {
|
|
|
+ reorderOption(paperStruct.getDefaultPaper());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 小题乱序
|
|
|
+ *
|
|
|
+ * @param defaultPaper
|
|
|
+ */
|
|
|
+ private void reorderQuestion(DefaultPaper defaultPaper) {
|
|
|
+ List<DefaultQuestionGroup> defaultQuestionGroupList = defaultPaper.getQuestionGroupList();
|
|
|
+ for (int i = 0; i < defaultQuestionGroupList.size(); i++) {
|
|
|
+ DefaultQuestionGroup defaultQuestionGroup = defaultQuestionGroupList.get(i);
|
|
|
+ if (checkObjectiveQuestionByGroup(defaultQuestionGroup)) {
|
|
|
+ List<DefaultQuestionStructureWrapper> questionStructureWrapperList = defaultQuestionGroup
|
|
|
+ .getQuestionWrapperList();
|
|
|
+ Collections.shuffle(questionStructureWrapperList);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选项乱序
|
|
|
+ *
|
|
|
+ * @param defaultPaper
|
|
|
+ */
|
|
|
+ private void reorderOption(DefaultPaper defaultPaper) {
|
|
|
+ List<DefaultQuestionGroup> defaultQuestionGroupList = defaultPaper.getQuestionGroupList();
|
|
|
+ // 遍历大题
|
|
|
+ for (int i = 0; i < defaultQuestionGroupList.size(); i++) {
|
|
|
+ DefaultQuestionGroup defaultQuestionGroup = defaultQuestionGroupList.get(i);
|
|
|
+ List<DefaultQuestionStructureWrapper> questionStructureWrapperList = defaultQuestionGroup
|
|
|
+ .getQuestionWrapperList();
|
|
|
+ // 遍历小题
|
|
|
+ for (DefaultQuestionStructureWrapper defaultQuestionStructureWrapper : questionStructureWrapperList) {
|
|
|
+ List<DefaultQuestionUnitWrapper> questionUnitWrapperList = defaultQuestionStructureWrapper
|
|
|
+ .getQuestionUnitWrapperList();
|
|
|
+ // 遍历题单元
|
|
|
+ for (DefaultQuestionUnitWrapper defaultQuestionUnitWrapper : questionUnitWrapperList) {
|
|
|
+ if (QuestionTypeUtil.isChoiceQuestion(defaultQuestionUnitWrapper.getQuestionType())) {
|
|
|
+ Integer[] optionPermutation = defaultQuestionUnitWrapper.getOptionPermutation();
|
|
|
+ defaultQuestionUnitWrapper.setOptionPermutation(CommonUtil.reorderArray(optionPermutation));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 小题乱序条件:一个大题下全是单一的客观题,例如全是单选,全是多选,全是判断 判断一个大题下是否是单一的客观题
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean checkObjectiveQuestionByGroup(DefaultQuestionGroup defaultQuestionGroup) {
|
|
|
+ Set<QuestionType> questionTypes = new HashSet<QuestionType>();
|
|
|
+ List<DefaultQuestionStructureWrapper> questionWrapperList = defaultQuestionGroup.getQuestionWrapperList();
|
|
|
+ for (int i = 0; i < questionWrapperList.size(); i++) {
|
|
|
+ List<DefaultQuestionUnitWrapper> questionUnitWrapperList = questionWrapperList.get(i)
|
|
|
+ .getQuestionUnitWrapperList();
|
|
|
+ if (questionUnitWrapperList.size() > 1) {
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ questionTypes.add(questionUnitWrapperList.get(0).getQuestionType());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (questionTypes.size() > 1) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ QuestionType questionType = questionTypes.iterator().next();
|
|
|
+ if (QuestionTypeUtil.isObjectiveQuestion(questionType)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算考试时长 校验是否达到冻结时间
|
|
|
+ *
|
|
|
+ * @param examSessionInfo
|
|
|
+ * @return
|
|
|
+ * @Param studentId
|
|
|
+ */
|
|
|
+ private Long checkAndComputeExamDuration(ExamSessionInfo examSessionInfo, Long studentId) {
|
|
|
+ long st = System.currentTimeMillis();
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ Long lastHeartbeat = examSessionInfo.getLastHeartbeat();
|
|
|
+ long usedTime = examSessionInfo.getHeartbeat() * 60 * 1000;// 考试时长=心跳次数*60*1000
|
|
|
+ if (lastHeartbeat == null) {
|
|
|
+ lastHeartbeat = now;
|
|
|
+ usedTime = 1 * 60 * 1000;// 算作1分钟
|
|
|
+ }
|
|
|
+
|
|
|
+ if (now - lastHeartbeat < 60 * 1000) {
|
|
|
+ // 如果最后一次心跳和当前时间小于1分钟,则把零碎的时间也加进去
|
|
|
+ usedTime += now - lastHeartbeat;
|
|
|
+ }
|
|
|
+ // 如果没有超过冻结时间,抛出异常
|
|
|
+ if (examSessionInfo.getExamType().equals(ExamType.ONLINE.name())) {
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ ExamingRecordEntity rec = GlobalHelper.getEntity(examingRecordRepo, examSessionInfo.getExamRecordId(), ExamingRecordEntity.class);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("1.1 [CHECK_AND_COMPUTE_EXAMD_URATION]获取状态为examing的进行中的考试记录 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rec != null && rec.getIsExceed() != null && rec.getIsExceed()) {// 超过断点最大次数的不校验冻结时间
|
|
|
+ return usedTime;
|
|
|
+ }
|
|
|
+ long freezeTime = examSessionInfo.getFreezeTime() * 60 * 1000;
|
|
|
+ if (usedTime < freezeTime) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-checkAndComputeExamDuration-exception",
|
|
|
+ "开考" + examSessionInfo.getFreezeTime() + "分钟后才能交卷");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("1.2 [CHECK_AND_COMPUTE_EXAMD_URATION]合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
+ }
|
|
|
+ return usedTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public EndExamPreInfo endExamPre(Long studentId) {
|
|
|
+ long st = System.currentTimeMillis();
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ // 获取考试会话,判断考生是否已结束考试
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(studentId);
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("1 [END_EXAM_PRE]获取考试会话 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ long examRecordDataId = examSessionInfo.getExamRecordDataId();
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ // 得到考试时长,校验是否达到冻结时间
|
|
|
+ long usedExamTime = checkAndComputeExamDuration(examSessionInfo, studentId);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("2 [END_EXAM_PRE]校验是否达到冻结时间 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ // 查询考试记录
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
|
|
|
+ ExamRecordDataEntity.class);
|
|
|
+ examRecordData.setUsedExamTime(usedExamTime);
|
|
|
+ EndExamPreInfo endExamPreInfo = new EndExamPreInfo();
|
|
|
+ endExamPreInfo.setStudentId(studentId);
|
|
|
+ endExamPreInfo.setExamRecordData(examRecordData);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("3 [END_EXAM_PRE]查询考试记录 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("4 [END_EXAM_PRE]合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
+ }
|
|
|
+ return endExamPreInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param examRecordData 考试记录对象
|
|
|
+ * @param handInExamType 交卷类型
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void handInExam(ExamRecordDataEntity examRecordData, HandInExamType handInExamType) {
|
|
|
+ //如果当前考试记录状态不为考试中,则直接返回
|
|
|
+ if (examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_ING) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ long st = System.currentTimeMillis();
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ ExamRecordEntity examRecord = GlobalHelper.getEntity(examRecordRepo, examRecordData.getExamRecordId(), ExamRecordEntity.class);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("1 [HAND_IN_EXAM]获取考试记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (handInExamType == HandInExamType.MANUAL) {
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
|
|
|
+ examRecordData.setEndTime(new Date());
|
|
|
+
|
|
|
+ //手工手卷时,如果开启人脸检测,则更新抓拍队列优先级
|
|
|
+ String isFaceEnable = CacheHelper.getExamOrgProperty(examRecord.getExamId(), examRecord.getOrgId(),
|
|
|
+ ExamProperties.IS_FACE_ENABLE.name()).getValue();
|
|
|
+ if (isFaceEnable != null && Constants.isTrue.equals(isFaceEnable)) {
|
|
|
+ //更新照片抓拍队列优先级为高优先级
|
|
|
+ examCaptureQueueRepo.updateExamCaptureQueuePriority(Constants.PROCESS_CAPTURE_HIGH_PRIORITY, examRecordData.getId());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("2.1 [HAND_IN_EXAM]更新照片抓拍队列优先级为高优先级耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (handInExamType == HandInExamType.AUTO) {
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_AUTO_HAND_IN);
|
|
|
+ examRecordData.setCleanTime(new Date());
|
|
|
+ } else {
|
|
|
+ throw new StatusException("201101", "暂不支持的交卷类型");
|
|
|
+ }
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ //将考试中的断点相关信息更新到考试记录表中
|
|
|
+ ExamingRecordEntity examingRecord = GlobalHelper.getEntity(examingRecordRepo, examRecordData.getExamRecordId(), ExamingRecordEntity.class);
|
|
|
+ examRecordData.setIsContinued(examingRecord.getIsContinued());
|
|
|
+ examRecordData.setContinuedCount(examingRecord.getContinuedCount());
|
|
|
+ examRecordData.setIsExceed(examingRecord.getIsExceed());
|
|
|
+
|
|
|
+ // 保存考试记录
|
|
|
+ examRecordData = examRecordDataRepo.save(examRecordData);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("2.2 [HAND_IN_EXAM]保存考试记录 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ // 计算保存考试分数
|
|
|
+ examScoreService.saveExamScore(examRecord,examRecordData);
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("3 [HAND_IN_EXAM]计算保存考试分数耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ //把进行中的考试记录放入交卷队列中
|
|
|
+ examRecordDataService.createHandInExamRecord(examRecord.getId(), examRecordData.getId(), examRecord.getStudentId());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("4 [HAND_IN_EXAM]把进行中的考试记录放入交卷队列中耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ //删除进行中的考试记录数据
|
|
|
+ examRecordDataService.deleteExamingRecord(examRecordData.getExamRecordId());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("5 [HAND_IN_EXAM]删除进行中的考试记录数据耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+
|
|
|
+ startTime = System.currentTimeMillis();
|
|
|
+ // 删除redis会话
|
|
|
+ examSessionInfoService.deleteExamSessionInfo(examRecord.getStudentId());
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("6 [HAND_IN_EXAM]删除redis会话:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
+ log.debug("7 [HAND_IN_EXAM]合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public boolean processAfterHandInExam(Long examRecordDataId, Long studentId, HandInExamType handInExamType) {
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
|
|
|
+
|
|
|
+ //如果考试记录状态为已处理,则直接返回true.
|
|
|
+ if (examRecordDataService.isExamRecordEnded(examRecordData)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ //判断是否存在未处理的图片
|
|
|
+ boolean existUnhandledExamCaptureQueue =
|
|
|
+ (examCaptureQueueRepo.existsUnhandledByExamRecordDataId(examRecordDataId) != null);
|
|
|
+ if (existUnhandledExamCaptureQueue) {
|
|
|
+ throw new StatusException(Constants.CAPTURE_PROCESSING_STATUS_CODE, "PROCESSING");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理照片上锁,处理抓拍照片已经完成,此时不允许再次上传抓拍照片,锁默认存在2分钟
|
|
|
+ redisClient.set(Constants.EXAM_CAPTURE_PHOTO_LOCK_PREFIX + examRecordDataId, 1, 120);
|
|
|
+
|
|
|
+ // 计算人脸检测结果(根据已检测出照片数据更新考试记录中的相关人脸检测的统计字段)
|
|
|
+ CalculateFaceCheckResultInfo calculateFaceCheckResultInfo = examRecordDataService
|
|
|
+ .calculateFaceCheckResult(examRecordData);
|
|
|
+ examRecordData = calculateFaceCheckResultInfo.getExamRecordData();
|
|
|
+ // 计算活体检测结果(根据已检测出照片数据更新考试记录中的相关人脸检测的统计字段)
|
|
|
+ examRecordData = examRecordDataService.calculateFaceLivenessVerifyResult(examRecordData);
|
|
|
+ // 违纪自动审核
|
|
|
+ examRecordData = examRecordDataService.examRecordAutoAudit(calculateFaceCheckResultInfo.getIsNoPhotoAndIllegality(),
|
|
|
+ examRecordData);
|
|
|
+
|
|
|
+ final ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordData.getId());
|
|
|
+ // 保存阅卷相关数据
|
|
|
+ examRecordForMarkingService.saveExamRecordForMarking(examRecordData, examScore.getObjectiveScore());
|
|
|
+
|
|
|
+ //更新考试记录相关数据
|
|
|
+ if (handInExamType == HandInExamType.MANUAL) {
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_END);
|
|
|
+ //根产品确认,自动服务清理时,不再更新考试结束时间,以交卷时的时间为准。
|
|
|
+ // examRecordData.setEndTime(new Date());
|
|
|
+ } else if (handInExamType == HandInExamType.AUTO) {
|
|
|
+ examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_OVERDUE);
|
|
|
+ examRecordData.setCleanTime(new Date());
|
|
|
+ } else {
|
|
|
+ throw new StatusException("201101", "暂不支持的交卷类型");
|
|
|
+ }
|
|
|
+ examRecordDataRepo.save(examRecordData);
|
|
|
+ // 保存考试分数数据到推分队列
|
|
|
+ examScorePushQueueService.saveScoreDataInfoToQueue(examRecordData, examScore.getId());
|
|
|
+ // 保存考试分数数据到分数获取队列
|
|
|
+ examScoreObtainQueueService.saveExamScoreObtainQueue(examRecordData);
|
|
|
+
|
|
|
+ //删除已交卷的考试记录数据
|
|
|
+ examRecordDataService.deleteHandInExamRecord(examRecordData.getExamRecordId());
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cleanTempFileAnswers() {
|
|
|
+ //默认删除7天以前的数据
|
|
|
+ int days = PropertyHolder.getInt("oe.cleanTempFileAnswer.thresholdDays", 7);
|
|
|
+ Date dateBeforeDays = DateUtils.addDays(new Date(), -1 * days);
|
|
|
+ examFileAnswerTempRepo.deleteByCreationTimeBefore(dateBeforeDays);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 考试心跳每分钟调用一次
|
|
|
+ *
|
|
|
+ * @param studentId 学生id
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public long examHeartbeat(Long studentId) {
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(studentId);
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ throw new StatusException("ExamControlServiceImpl-examHeartbeat-exception", "会话已过期,请离开考试!");
|
|
|
+ }
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ if (examSessionInfo.getLastHeartbeat() != null) {
|
|
|
+ if (now - examSessionInfo.getLastHeartbeat() >= examSessionInfo.getExamReconnectTime() * 60 * 1000) {
|
|
|
+ // 如果当前时间和上一次心跳间隔超过了断点续考时间,则删除考试会话,并停止心跳
|
|
|
+ examSessionInfoService.deleteExamSessionInfo(studentId);
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 记录最后心跳时间
|
|
|
+ examSessionInfo.setLastHeartbeat(now);
|
|
|
+ // 更新心跳次数
|
|
|
+ examSessionInfo.setHeartbeat(examSessionInfo.getHeartbeat() + 1);
|
|
|
+ // 如果进行过人脸活体检测,过期时间加1分钟
|
|
|
+ Long faceVerifyTimes = examFaceLivenessVerifyRepo
|
|
|
+ .countByExamRecordDataId(examSessionInfo.getExamRecordDataId());
|
|
|
+ int addTimes = faceVerifyTimes == null ? 0 : 1;
|
|
|
+ // 会话过期时间:秒
|
|
|
+ int expireTime = (examSessionInfo.getExamReconnectTime() + addTimes + 1) * 60;
|
|
|
+ // 更新考试会话过期时间
|
|
|
+ examSessionInfoService.saveExamSessionInfo(studentId, examSessionInfo, expireTime);
|
|
|
+ // 返回考试剩余时间 :考试时长-心跳次数*60000
|
|
|
+ return examSessionInfo.getExamDuration() - (examSessionInfo.getHeartbeat() * 60 * 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CheckExamInProgressInfo checkExamInProgress(Long studentId) {
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(studentId);
|
|
|
+ // 检查考试会话是否存在,或者是否失效,如果没有失效,则返回考试中的考试记录实体,否则直接返回null
|
|
|
+ ExamingRecordEntity examingRecord = checkExamSession(examSessionInfo, studentId);
|
|
|
+ if (examingRecord == null) {
|
|
|
+ return null;
|
|
|
+ } else {
|
|
|
+ ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examSessionInfo.getExamStudentId());
|
|
|
+ Integer maxInterruptNum = getMaxInterruptNum(examStudentEntity.getExamId(), examStudentEntity.getRootOrgId(), examStudentEntity.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);
|
|
|
+ }
|
|
|
+ //更新考试中的断点续考属性
|
|
|
+ examingRecordRepo.save(examingRecord);
|
|
|
+
|
|
|
+ checkExamInProgressInfo.setExamRecordDataId(examingRecord.getExamRecordDataId());
|
|
|
+ checkExamInProgressInfo.setExamId(examSessionInfo.getExamId());
|
|
|
+ checkExamInProgressInfo.setUsedTime(examSessionInfo.getHeartbeat() * 60 * 1000);
|
|
|
+ checkExamInProgressInfo.setMaxInterruptNum(maxInterruptNum);
|
|
|
+ checkExamInProgressInfo.setInterruptNum(examingRecord.getContinuedCount());
|
|
|
+
|
|
|
+ // 断点续考时重新计算活体检测的分钟数
|
|
|
+ checkExamInProgressInfo.setFaceVerifyMinute(examFaceLivenessVerifyService.getFaceLivenessVerifyMinute(
|
|
|
+ examStudentEntity.getOrgId(), examStudentEntity.getExamId(),
|
|
|
+ examingRecord.getExamRecordDataId(), examSessionInfo.getHeartbeat()));
|
|
|
+ return checkExamInProgressInfo;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private ExamingRecordEntity checkExamSession(ExamSessionInfo examSessionInfo, Long studentId) {
|
|
|
+ ExamingRecordEntity examingRecord = examingRecordRepo.findOnlineExamingRecord(studentId);
|
|
|
+ // 如果考试记录不存在,清除会话
|
|
|
+ if (examingRecord == null) {
|
|
|
+ examSessionInfoService.deleteExamSessionInfo(studentId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果会话不存在,自动交卷
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ // 查询考试记录
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examingRecord.getExamRecordDataId(),
|
|
|
+ ExamRecordDataEntity.class);
|
|
|
+ handInExam(examRecordData, HandInExamType.AUTO);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ // 如果已经过了断点续考时间,清除考试记录,清除会话
|
|
|
+ Long lastHeartbeat = examSessionInfo.getLastHeartbeat();
|
|
|
+ if (lastHeartbeat == null) {
|
|
|
+ lastHeartbeat = examSessionInfo.getStartTime();
|
|
|
+ }
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ if (now - lastHeartbeat >= examSessionInfo.getExamReconnectTime().intValue() * 60 * 1000) {
|
|
|
+ examSessionInfoService.deleteExamSessionInfo(studentId);
|
|
|
+ ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo,
|
|
|
+ examingRecord.getExamRecordDataId(), ExamRecordDataEntity.class);
|
|
|
+ handInExam(examRecordDataEntity, HandInExamType.AUTO);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return examingRecord;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cleanExamingRecord(ExamingRecordEntity examingRecord) {
|
|
|
+
|
|
|
+ //只有考试会话结束的考试才执行清理动作
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(examingRecord.getStudentId());
|
|
|
+ if (examSessionInfo != null) {
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_EXAMING_RECORD_" + examingRecord.getExamRecordDataId() + "]考试未结束,不执行清理.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examingRecord.getExamRecordDataId(), ExamRecordDataEntity.class);
|
|
|
+
|
|
|
+ //只有考试状态为考试中,系统才需要执行交卷动作,因为有可能已经手动交卷,所以这里再做一次判断
|
|
|
+ if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_ING) {
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_EXAMING_RECORD_" + examRecordData.getId() + "]状态为进行中考试,执行交卷[handInExam],.");
|
|
|
+ handInExam(examRecordData, HandInExamType.AUTO);
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_EXAMING_RECORD_" + examRecordData.getId() + "]考试记录交卷完成.");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cleanHandInExamRecord(HandInExamRecordEntity handInExamRecord) {
|
|
|
+ ExamRecordEntity examRecord = GlobalHelper.getEntity(examRecordRepo, handInExamRecord.getId(), ExamRecordEntity.class);
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, handInExamRecord.getExamRecordDataId(), ExamRecordDataEntity.class);
|
|
|
+
|
|
|
+ //只清理状态为已交卷的数据,才能执行交卷后续动作
|
|
|
+ if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_AUTO_HAND_IN ||
|
|
|
+ examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_HAND_IN) {
|
|
|
+ //默认自动交卷
|
|
|
+ HandInExamType handInExamType = HandInExamType.AUTO;
|
|
|
+ if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_AUTO_HAND_IN) {
|
|
|
+ handInExamType = HandInExamType.AUTO;
|
|
|
+ } else if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_HAND_IN) {
|
|
|
+ handInExamType = HandInExamType.MANUAL;
|
|
|
+ }
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_HAND_IN_EXAM_RECORD_" + examRecordData.getId() + "]开始执行交卷后续动作,.");
|
|
|
+ try {
|
|
|
+ processAfterHandInExam(handInExamRecord.getExamRecordDataId(), examRecord.getStudentId(), handInExamType);
|
|
|
+ } catch (StatusException e) {
|
|
|
+ if (e.getCode().equals(Constants.CAPTURE_PROCESSING_STATUS_CODE)) {
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_HAND_IN_EXAM_RECORD_" + examRecordData.getId() + "]有未处理完成的图片,稍侯重试...");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cleanExamRecordTaskLog.debug("[CLEAN_HAND_IN_EXAM_RECORD_" + examRecordData.getId() + "]考试记录交卷后续动作完成.");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public EndExamInfo getEndExamInfo(Long examRecordDataId) {
|
|
|
+ ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ if (examScore == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
|
|
|
+ ExamRecordDataEntity.class);
|
|
|
+ //如果考试没有最终结束,则返回空值
|
|
|
+ if (examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_END &&
|
|
|
+ examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_OVERDUE) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ EndExamInfo endExamInfo = new EndExamInfo();
|
|
|
+ endExamInfo.setExamRecordDataId(examRecordDataId);
|
|
|
+ endExamInfo.setIsWarn(examRecordData.getIsWarn());// 是否异常数据
|
|
|
+ endExamInfo.setObjectiveScore(examScore.getObjectiveScore());// 客观题总分
|
|
|
+ endExamInfo.setObjectiveAccuracy(examScore.getObjectiveAccuracy());// 客观点答对比率
|
|
|
+ return endExamInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void handleByExamCaptureQueueFailedDispose(Long examRecordDataId) {
|
|
|
+ // 查询考试记录
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
|
|
|
+ ExamRecordDataEntity.class);
|
|
|
+ // 计算人脸检测结果
|
|
|
+ CalculateFaceCheckResultInfo calculateFaceCheckResultInfo = examRecordDataService
|
|
|
+ .calculateFaceCheckResult(examRecordData);
|
|
|
+ examRecordData = calculateFaceCheckResultInfo.getExamRecordData();
|
|
|
+ examRecordDataRepo.save(examRecordData);
|
|
|
+ // 保存阅卷相关数据
|
|
|
+ ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ examRecordForMarkingService.saveExamRecordForMarking(examRecordData, examScore.getObjectiveScore());
|
|
|
+ // 保存考试分数数据到推分队列
|
|
|
+ examScorePushQueueService.saveScoreDataInfoToQueue(examRecordData, examScore.getId());
|
|
|
+ // 保存考试分数数据到分数获取队列
|
|
|
+ examScoreObtainQueueService.saveExamScoreObtainQueue(examRecordData);
|
|
|
+ // 发送获取分数通知
|
|
|
+
|
|
|
+ ExamRecordEntity examRecord = GlobalHelper.getEntity(
|
|
|
+ examRecordRepo, examRecordData.getExamRecordId(), ExamRecordEntity.class);
|
|
|
+ examScoreObtainQueueService.sendObtainScoreNotify(examRecord.getRootOrgId());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public UpyunSignatureInfo getUpyunSignature(GetUpyunSignatureReq req) {
|
|
|
+ UpyunSignatureInfo u = new UpyunSignatureInfo();
|
|
|
+ try {
|
|
|
+ ExamRecordDataEntity reco = GlobalHelper.getEntity(examRecordDataRepo, Long.valueOf(req.getExamRecordDataId()), ExamRecordDataEntity.class);
|
|
|
+ String md5 = req.getFileMd5();
|
|
|
+ Date signDate = null;
|
|
|
+ Date now = new Date();
|
|
|
+ Date expirationDate = DateUtils.addSeconds(now, SIGN_TIMEOUT);
|
|
|
+
|
|
|
+ StringBuffer filePath = new StringBuffer();
|
|
|
+
|
|
|
+ ExamRecordEntity examRecord = GlobalHelper.getEntity(
|
|
|
+ examRecordRepo, reco.getExamRecordId(), ExamRecordEntity.class);
|
|
|
+ synchronized (lock) {
|
|
|
+ filePath.append(SEPARATOR).append(OE_ANSWER_FILE_PATH).append(SEPARATOR).append(examRecord.getExamStudentId())
|
|
|
+ .append(SEPARATOR).append(req.getExamRecordDataId()).append(SEPARATOR).append(req.getOrder())
|
|
|
+ .append(SEPARATOR).append(examRecord.getExamStudentId()).append(UNDERLINE)
|
|
|
+ .append(req.getExamRecordDataId()).append(UNDERLINE).append(req.getOrder()).append(UNDERLINE)
|
|
|
+ .append(System.currentTimeMillis());
|
|
|
+ Util.sleep(TimeUnit.MILLISECONDS, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isNotEmpty(req.getExt())) {
|
|
|
+ filePath.append(UNDERLINE).append(req.getExt());
|
|
|
+ }
|
|
|
+ filePath.append(".").append(req.getFileSuffix());
|
|
|
+
|
|
|
+ long expiration = expirationDate.getTime() / 1000;
|
|
|
+ String policy = policy(bucketName, expiration, filePath.toString(), signDate, md5);
|
|
|
+ String sign = sign("POST", getGMTDate(signDate), bucketName, policy, userName, UpYunUtils.md5(password),
|
|
|
+ md5);
|
|
|
+ u.setPolicy(policy);
|
|
|
+ u.setSignature(sign);
|
|
|
+ u.setFilePath(filePath.toString());
|
|
|
+ u.setUploadUrl(UrlUtil.joinUrl(bucketUrl, bucketName));
|
|
|
+ u.setUpyunFileDomain(upyunFileUrl);
|
|
|
+ } catch (UpException e) {
|
|
|
+ throw new StatusException("100003", "获取又拍云签名失败");
|
|
|
+ }
|
|
|
+ return u;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getGMTDate(Date d) {
|
|
|
+ if (d == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ SimpleDateFormat formater = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
|
|
|
+ formater.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
|
|
+ return formater.format(d);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @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) {
|
|
|
+ Map<String, Object> paramMap = new HashMap<String, Object>();
|
|
|
+ // 不能为空
|
|
|
+ paramMap.put("bucket", bucketName);
|
|
|
+ Date expirationDate = DateUtils.addSeconds(new Date(), SIGN_TIMEOUT);
|
|
|
+ // 不能为空
|
|
|
+ paramMap.put("expiration", expirationDate.getTime() / 1000);
|
|
|
+ paramMap.put("save-key", filePath);
|
|
|
+ // 为空时,以又怕云时间和expiration比较,不为空时以此date和expiration比较
|
|
|
+ paramMap.put("date", date);
|
|
|
+ // 可以为空
|
|
|
+ paramMap.put("content-md5", md5);
|
|
|
+ String policy = UpYunUtils.getPolicy(paramMap);
|
|
|
+ return policy;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 必须和policy中date一致,可以都为空.GMTDate
|
|
|
+ *
|
|
|
+ * @param method
|
|
|
+ * @param date
|
|
|
+ * @param bucketName
|
|
|
+ * @param policy
|
|
|
+ * @param userName
|
|
|
+ * @param password
|
|
|
+ * @param md5
|
|
|
+ * @return
|
|
|
+ * @throws UpException
|
|
|
+ */
|
|
|
+ private String sign(String method, String date, String bucketName, String policy, String userName, String password,
|
|
|
+ String md5) throws UpException {
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ String sp = "&";
|
|
|
+ sb.append(method);
|
|
|
+ sb.append(sp);
|
|
|
+ sb.append(SEPARATOR + bucketName);
|
|
|
+ if (date != null) {
|
|
|
+ sb.append(sp);
|
|
|
+ sb.append(date);
|
|
|
+ }
|
|
|
+ sb.append(sp);
|
|
|
+ sb.append(policy);
|
|
|
+ if (md5 != null && md5.length() > 0) {
|
|
|
+ sb.append(sp);
|
|
|
+ sb.append(md5);
|
|
|
+ }
|
|
|
+ String raw = sb.toString().trim();
|
|
|
+ byte[] hmac = null;
|
|
|
+ try {
|
|
|
+ hmac = UpYunUtils.calculateRFC2104HMACRaw(password, raw);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new UpException("calculate SHA1 wrong.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hmac != null) {
|
|
|
+ return "UPYUN " + userName + ":" + Base64Coder.encodeLines(hmac).trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getQrCode(@Valid GetQrCodeReq req, String key) {
|
|
|
+ int sessionTimeout = PropertyHolder.getInt(SESSION_TIMEOUT, 3600);
|
|
|
+ User user = redisClient.get(key, User.class, sessionTimeout);
|
|
|
+ if (null == user) {
|
|
|
+ throw new StatusException("100007", "登录信息已失效");
|
|
|
+ }
|
|
|
+ return getQrCode(req, user);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getQrCode(GetQrCodeReq req, User user) {
|
|
|
+ if (user == null) {
|
|
|
+ throw new StatusException("100002", "登录信息错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果是调用环境监测的接口,不用做如下校验
|
|
|
+ if (!req.isTestEnv()) {
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService
|
|
|
+ .getExamSessionInfo(user.getUserId());
|
|
|
+
|
|
|
+ ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(req.getExamStudentId());
|
|
|
+ if (examStudentEntity == null) {
|
|
|
+ throw new StatusException("100012", "考生不存在");
|
|
|
+ }
|
|
|
+ if (!user.getUserId().equals(examStudentEntity.getStudentId())) {
|
|
|
+ throw new StatusException("100013", "无效的请求");
|
|
|
+ }
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ throw new StatusException("100006", "考试已结束");
|
|
|
+ }
|
|
|
+ if (examSessionInfo.getExamRecordDataId().longValue() != req.getExamRecordDataId().longValue()
|
|
|
+ || examSessionInfo.getExamStudentId().longValue() != req.getExamStudentId().longValue()) {
|
|
|
+ throw new StatusException("100008", "无效的请求");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ //环境检测时,需要重新给考生id赋值
|
|
|
+ ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, req.getExamRecordDataId(),
|
|
|
+ ExamRecordDataEntity.class);
|
|
|
+ ExamRecordEntity examingRecord = GlobalHelper.getEntity(examRecordRepo, examRecordData.getExamRecordId(),
|
|
|
+ ExamRecordEntity.class);
|
|
|
+ req.setExamStudentId(examingRecord.getExamStudentId());
|
|
|
+ }
|
|
|
+ String key = user.getKey();
|
|
|
+ StringBuffer param = new StringBuffer();
|
|
|
+ String transferFileType = StringUtils.isBlank(req.getTransferFileType()) ? "" : req.getTransferFileType();
|
|
|
+ param.append("examStudentId=").append(req.getExamStudentId()).append("&examRecordDataId=")
|
|
|
+ .append(req.getExamRecordDataId()).append("&order=").append(req.getOrder())
|
|
|
+ .append("&transferFileType=").append(transferFileType).append("&key=").append(key);
|
|
|
+ // 需要签名的参数
|
|
|
+ StringBuffer sourStr = new StringBuffer();
|
|
|
+ sourStr.append(req.getOrder()).append(UNDERLINE).append(req.getExamRecordDataId()).append(UNDERLINE)
|
|
|
+ .append(key).append(UNDERLINE).append(req.getExamStudentId());
|
|
|
+ // 签名
|
|
|
+ byte[] bytes = SHA256.encode(sourStr.toString());
|
|
|
+ String hexAscii = ByteUtil.toHexAscii(bytes);
|
|
|
+ param.append("&token=").append(hexAscii);
|
|
|
+ String qrStr;
|
|
|
+ try {
|
|
|
+ qrStr = audioAppUrl + SEPARATOR + URLEncoder.encode(param.toString(), "UTF-8");
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ throw new StatusException("100001", "参数编码异常");
|
|
|
+ }
|
|
|
+ return qrStr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过websocket发送消息
|
|
|
+ *
|
|
|
+ * @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) throws Exception {
|
|
|
+ Map<String, Object> data = new HashMap<String, Object>();
|
|
|
+ data.put("examRecordDataId", examRecordDataId);
|
|
|
+ data.put("order", order);
|
|
|
+ data.put("fileUrl", fileUrl);
|
|
|
+ data.put("transferFileType", transferFileType);
|
|
|
+
|
|
|
+ SendFileAnswerMessageReq sendMessageReq = new SendFileAnswerMessageReq();
|
|
|
+ Long clientId;
|
|
|
+ //如果是环境检测,则使用用户id(即学生id)作为clientId
|
|
|
+ if (isTestDev(examRecordDataId)) {
|
|
|
+ clientId = userId;
|
|
|
+ }
|
|
|
+ //不是环境检测,仍然使用考试记录id作为clientId
|
|
|
+ else {
|
|
|
+ clientId = examRecordDataId;
|
|
|
+ }
|
|
|
+ sendMessageReq.setClientId(clientId);
|
|
|
+ sendMessageReq.setEventType(WebSocketEventType.GET_FILE_ANSWER.toString());
|
|
|
+ sendMessageReq.setIsSuccess(true);
|
|
|
+ sendMessageReq.setData(data);
|
|
|
+ fileAnswerWebsocketCloudService.sendFileAnswerMessage(sendMessageReq);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过websocket发送二维码扫描信息
|
|
|
+ *
|
|
|
+ * @param clientId
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param order
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void sendScanQrCodeToWebSocket(String clientId, Long examRecordDataId, Integer order) throws Exception {
|
|
|
+ Map<String, Object> data = new HashMap<String, Object>();
|
|
|
+ data.put("examRecordDataId", examRecordDataId);
|
|
|
+ data.put("order", order);
|
|
|
+ data.put("scanStatus", "SCANNED");
|
|
|
+ SendScanQrCodeMessageReq sendScanQrCodeMessageReq = new SendScanQrCodeMessageReq();
|
|
|
+ sendScanQrCodeMessageReq.setExamRecordDataId(examRecordDataId);
|
|
|
+ sendScanQrCodeMessageReq.setEventType(WebSocketEventType.SCAN_QR_CODE.toString());
|
|
|
+ sendScanQrCodeMessageReq.setIsSuccess(true);
|
|
|
+ sendScanQrCodeMessageReq.setClientId(clientId);
|
|
|
+ sendScanQrCodeMessageReq.setData(data);
|
|
|
+ fileAnswerWebsocketCloudService.sendScanQrCodeMessage(sendScanQrCodeMessageReq);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CheckQrCodeInfo checkQrCode(String param) {
|
|
|
+ String str;
|
|
|
+ str = UrlUtil.decode(param);
|
|
|
+ Map<String, String> map = Splitter.on("&").withKeyValueSeparator("=").split(str);
|
|
|
+ String examStudentId = map.get("examStudentId");
|
|
|
+ String examRecordDataId = map.get("examRecordDataId");
|
|
|
+ String order = map.get("order");
|
|
|
+ String key = map.get("key");
|
|
|
+ String token = map.get("token");
|
|
|
+ // 需要签名的参数
|
|
|
+ StringBuffer sourStr = new StringBuffer();
|
|
|
+ sourStr.append(order).append(UNDERLINE).append(examRecordDataId).append(UNDERLINE).append(key)
|
|
|
+ .append(UNDERLINE).append(examStudentId);
|
|
|
+ // 签名
|
|
|
+ byte[] bytes = SHA256.encode(sourStr.toString());
|
|
|
+ String hexAscii = ByteUtil.toHexAscii(bytes);
|
|
|
+ if (!hexAscii.equals(token)) {
|
|
|
+ throw new StatusException("100005", "无效的二维码");
|
|
|
+ }
|
|
|
+
|
|
|
+ //判断考生是否存在
|
|
|
+ ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(Long.valueOf(examStudentId));
|
|
|
+ if (examStudentEntity == null) {
|
|
|
+ throw new StatusException("100012", "考生不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ String clientId;
|
|
|
+ //未开启环境检测,才进行如下校验
|
|
|
+ if (!isTestDev(Long.valueOf(examRecordDataId))) {
|
|
|
+ //非环境检测,clientId即examRecordDataId
|
|
|
+ clientId = examRecordDataId;
|
|
|
+ // 判断考试是否结束
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService
|
|
|
+ .getExamSessionInfo(examStudentEntity.getStudentId());
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ throw new StatusException("100006", "考试已结束");
|
|
|
+ }
|
|
|
+ if (examSessionInfo.getExamRecordDataId().longValue() != Long.valueOf(examRecordDataId).longValue()
|
|
|
+ || examSessionInfo.getExamStudentId().longValue() != Long.valueOf(examStudentId).longValue()) {
|
|
|
+ throw new StatusException("100008", "无效的二维码");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ //环境检测时,clientId即用户id
|
|
|
+ clientId = key.substring(key.lastIndexOf("_") + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验通过
|
|
|
+ CheckQrCodeInfo res = new CheckQrCodeInfo();
|
|
|
+ res.setExamRecordDataId(Long.valueOf(examRecordDataId));
|
|
|
+ res.setExamStudentId(Long.valueOf(examStudentId));
|
|
|
+ res.setKey(key);
|
|
|
+ int sessionTimeout = PropertyHolder.getInt(SESSION_TIMEOUT, 3600);
|
|
|
+ User user = redisClient.get(key, User.class, sessionTimeout);
|
|
|
+ if (null == user) {
|
|
|
+ throw new StatusException("100007", "登录信息已失效");
|
|
|
+ }
|
|
|
+ res.setToken(user.getToken());
|
|
|
+ CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
|
|
|
+
|
|
|
+ res.setCourseId(courseBean.getId());
|
|
|
+ res.setCourseName(courseBean.getName());
|
|
|
+ ExamRecordQuestionsEntity examRecordQuestionsEntity = examRecordQuestionsService.
|
|
|
+ getExamRecordQuestionsAndFixExamRecordDataIfNecessary(Long.valueOf(examRecordDataId));
|
|
|
+ List<ExamQuestionEntity> examQuestionList = examRecordQuestionsEntity.getExamQuestionEntities();
|
|
|
+
|
|
|
+ if (examRecordQuestionsEntity == null || examQuestionList == null || examQuestionList.isEmpty()) {
|
|
|
+ throw new StatusException("100008", "无效的二维码");
|
|
|
+ }
|
|
|
+ List<ExamQuestionEntity> filterList = examQuestionList.stream().filter(p ->
|
|
|
+ p.getOrder().equals(Integer.valueOf(order))).collect(Collectors.toList());
|
|
|
+ if (filterList == null || filterList.isEmpty()) {
|
|
|
+ throw new StatusException("100008", "无效的二维码");
|
|
|
+ }
|
|
|
+ ExamQuestionEntity eqe = filterList.get(0);
|
|
|
+
|
|
|
+ res.setQuestionOrder(eqe.getOrder());
|
|
|
+ res.setQuestionMainNumber(eqe.getMainNumber());
|
|
|
+ res.setSubNumber(getSubNumber(examRecordQuestionsEntity,Integer.valueOf(order)));
|
|
|
+ try {
|
|
|
+ this.sendScanQrCodeToWebSocket(clientId, Long.valueOf(examRecordDataId), Integer.valueOf(order));
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new StatusException("100011", "消息通知失败");
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer getSubNumber(ExamRecordQuestionsEntity examRecordQuestionsEntity,Integer order) {
|
|
|
+ List<UploadedFileAnswerInfo> list = getReSortedQuestionList(examRecordQuestionsEntity);
|
|
|
+ for (UploadedFileAnswerInfo info : list) {
|
|
|
+ if (order.intValue() == info.getOrder().intValue()) {
|
|
|
+ return info.getSubNumber();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public ExamFileAnswerTempEntity saveUploadedFile(SaveUploadedFileReq req, User user) {
|
|
|
+ // 判断考试是否结束
|
|
|
+ ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(req.getExamStudentId());
|
|
|
+ if (examStudentEntity == null) {
|
|
|
+ throw new StatusException("100012", "考生不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ //开启了环境检测,则不需要进行如下的校验
|
|
|
+ if (!isTestDev(req.getExamRecordDataId())) {
|
|
|
+ if (!user.getUserId().equals(examStudentEntity.getStudentId())) {
|
|
|
+ throw new StatusException("100013", "无效的请求");
|
|
|
+ }
|
|
|
+ ExamSessionInfo examSessionInfo = examSessionInfoService
|
|
|
+ .getExamSessionInfo(examStudentEntity.getStudentId());
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ throw new StatusException("100006", "考试已结束");
|
|
|
+ }
|
|
|
+ if (examSessionInfo.getExamRecordDataId().longValue() != req.getExamRecordDataId().longValue()
|
|
|
+ || examSessionInfo.getExamStudentId().longValue() != req.getExamStudentId().longValue()) {
|
|
|
+ throw new StatusException("100008", "考试已结束");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ExamFileAnswerTempEntity et = new ExamFileAnswerTempEntity();
|
|
|
+ et.setExamRecordDataId(req.getExamRecordDataId());
|
|
|
+ et.setExamStudentId(req.getExamStudentId());
|
|
|
+ et.setQuestionOrder(req.getOrder());
|
|
|
+ et.setFilePath(req.getFilePath());
|
|
|
+ et.setStatus(FileAnswerAcknowledgeStatus.UNCONFIRMED);
|
|
|
+ et.setTransferFileType(req.getTransferFileType());
|
|
|
+ return examFileAnswerTempRepo.save(et);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getUploadedFileAcknowledgeStatus(GetUploadedFileAcknowledgeStatusReq req) {
|
|
|
+ ExamFileAnswerTempEntity et = GlobalHelper.getEntity(examFileAnswerTempRepo,
|
|
|
+ req.getAcknowledgeId(), ExamFileAnswerTempEntity.class);
|
|
|
+ if (et != null) {
|
|
|
+ return et.getStatus().toString();
|
|
|
+ }
|
|
|
+ return FileAnswerAcknowledgeStatus.UNCONFIRMED.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void saveUploadedFileAcknowledgeStatus(SaveUploadedFileAcknowledgeStatusReq req) {
|
|
|
+ ExamFileAnswerTempEntity et = examFileAnswerTempRepo
|
|
|
+ .findByExamRecordDataIdAndQuestionOrderAndFilePath(req.getExamRecordDataId(), req.getOrder(), req.getFilePath().replace(upyunFileUrl, ""));
|
|
|
+ if (et == null) {
|
|
|
+ throw new StatusException("100010", "无效的数据");
|
|
|
+ }
|
|
|
+ et.setStatus(FileAnswerAcknowledgeStatus.valueOf(req.getAcknowledgeStatus()));
|
|
|
+ examFileAnswerTempRepo.save(et);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void deleteExamFileAnswerTemp(SaveUploadedFileReq req) {
|
|
|
+ examFileAnswerTempRepo.delete(req.getExamRecordDataId(), req.getExamStudentId(), req.getOrder(), req.getFilePath());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<UploadedFileAnswerInfo> getUploadedFileAnswerList(@Valid GetUploadedFileAnswerListReq req) {
|
|
|
+ List<UploadedFileAnswerInfo> resultList = new ArrayList<UploadedFileAnswerInfo>();
|
|
|
+ /*//未分组集合
|
|
|
+ List<ExamFileAnswerTempEntity> uploadedFileAnswerList = examFileAnswerTempRepo
|
|
|
+ .findByExamRecordDataIdAndStatus(req.getExamRecordDataId(), FileAnswerAcknowledgeStatus.CONFIRMED);
|
|
|
+ if (null == uploadedFileAnswerList || uploadedFileAnswerList.isEmpty()) {
|
|
|
+ return resultList;
|
|
|
+ } else {
|
|
|
+ //新集合,只取同一考试记录id和题号下的最新记录
|
|
|
+ List<ExamFileAnswerTempEntity> newestUploadedFileAnswerList = new ArrayList<>();
|
|
|
+ for (ExamFileAnswerTempEntity temp : uploadedFileAnswerList) {
|
|
|
+ boolean existData = newestUploadedFileAnswerList.stream().anyMatch(p ->
|
|
|
+ p.getQuestionOrder().equals(temp.getQuestionOrder())
|
|
|
+ && p.getExamRecordDataId().equals(temp.getExamRecordDataId()));
|
|
|
+ if (!existData) {
|
|
|
+ newestUploadedFileAnswerList.add(temp);
|
|
|
+ } else {
|
|
|
+ ExamFileAnswerTempEntity currentTemp = newestUploadedFileAnswerList.stream().filter(p -> p.getQuestionOrder().equals(temp.getQuestionOrder())
|
|
|
+ && p.getExamRecordDataId().equals(temp.getExamRecordDataId())).collect(Collectors.toList()).get(0);
|
|
|
+ //集合中取id最大的值
|
|
|
+ if (currentTemp.getId() < temp.getId()) {
|
|
|
+ newestUploadedFileAnswerList.remove(currentTemp);
|
|
|
+ newestUploadedFileAnswerList.add(temp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //按大题号给所有的小题定义序号
|
|
|
+ List<UploadedFileAnswerInfo> allQuestionAnswerList = getReSortedQuestionList(req.getExamRecordDataId());
|
|
|
+ //当前考生已作答的题目集合
|
|
|
+ List<ExamQuestionEntity> examQuestionsInMongo = examQuestionRepo.findByExamRecordDataId(req.getExamRecordDataId());
|
|
|
+ newestUploadedFileAnswerList.forEach(p -> {
|
|
|
+ //过滤出所有的音频题
|
|
|
+ List<UploadedFileAnswerInfo> uploadedFileAnswerInfoList = allQuestionAnswerList.stream().filter(pa -> pa.getOrder().equals(p.getQuestionOrder())).collect(Collectors.toList());
|
|
|
+ if (null != uploadedFileAnswerInfoList && !uploadedFileAnswerInfoList.isEmpty()) {
|
|
|
+ UploadedFileAnswerInfo info = uploadedFileAnswerInfoList.get(0);
|
|
|
+// //过滤掉重置过答案的数据 case 1 采用case1还是case2需要找张营确认一下
|
|
|
+// if(!examQuestionsInMongo.stream().anyMatch(pm->pm.getOrder().equals(info.getOrder())
|
|
|
+// && StringUtils.isEmpty(pm.getStudentAnswer()))) {
|
|
|
+// info.setExamRecordDataId(p.getExamRecordDataId());
|
|
|
+// //由于有可能学生答案在PC端展示时还未提交到服务器,所以必须用临时表中的数据
|
|
|
+// info.setStudentAnswer(upyunFileUrl+ p.getFilePath());
|
|
|
+// resultList.add(info);
|
|
|
+// }
|
|
|
+ //必须是考生作答过,且答案不为空,才能展示 case 2
|
|
|
+ if (examQuestionsInMongo.stream().anyMatch(pm -> pm.getOrder().equals(info.getOrder())
|
|
|
+ && !StringUtils.isEmpty(pm.getStudentAnswer()))) {
|
|
|
+ info.setExamRecordDataId(p.getExamRecordDataId());
|
|
|
+ //由于有可能学生答案在PC端展示时还未提交到服务器,所以必须用临时表中的数据
|
|
|
+ info.setStudentAnswer(upyunFileUrl + p.getFilePath());
|
|
|
+ resultList.add(info);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ //最后按序号从小到大再排一次
|
|
|
+ resultList.sort(Comparator.comparing(UploadedFileAnswerInfo::getOrder));*/
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据考试记录id获取所有已经重排序号的考试列表
|
|
|
+ * @param examRecordQuestionsEntity
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<UploadedFileAnswerInfo> getReSortedQuestionList(ExamRecordQuestionsEntity examRecordQuestionsEntity) {
|
|
|
+ //所有试题集合,包括非音频题
|
|
|
+ List<UploadedFileAnswerInfo> resultList = new ArrayList<UploadedFileAnswerInfo>();
|
|
|
+
|
|
|
+ if (null == examRecordQuestionsEntity || null == examRecordQuestionsEntity.getExamQuestionEntities() ||
|
|
|
+ examRecordQuestionsEntity.getExamQuestionEntities().isEmpty()) {
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ExamQuestionEntity> examQuestionAnswerList = examRecordQuestionsEntity.getExamQuestionEntities();
|
|
|
+ //按大题号分组并排序
|
|
|
+ Map<Integer, List<ExamQuestionEntity>> sortedMainNumberList = examQuestionAnswerList.stream().
|
|
|
+ sorted(Comparator.comparing(ExamQuestionEntity::getMainNumber)).
|
|
|
+ collect(Collectors.groupingBy(ExamQuestionEntity::getMainNumber, Collectors.toList()));
|
|
|
+ for (Integer mainNum : sortedMainNumberList.keySet()) {
|
|
|
+ //当前大题下的小题集合
|
|
|
+ List<ExamQuestionEntity> subEmptyExamQuestionAnswerList = sortedMainNumberList.get(mainNum);
|
|
|
+ //按order对小题进行重新排序
|
|
|
+ subEmptyExamQuestionAnswerList.sort(Comparator.comparing(ExamQuestionEntity::getOrder));
|
|
|
+ for (int i = 0; i < subEmptyExamQuestionAnswerList.size(); i++) {
|
|
|
+ ExamQuestionEntity curExamQuestion = subEmptyExamQuestionAnswerList.get(i);
|
|
|
+ UploadedFileAnswerInfo info = new UploadedFileAnswerInfo();
|
|
|
+ info.setExamRecordDataId(curExamQuestion.getExamRecordDataId());
|
|
|
+ info.setMainNumber(curExamQuestion.getMainNumber());
|
|
|
+ info.setOrder(curExamQuestion.getOrder());
|
|
|
+// info.setQuestionId(curExamQuestion.getQuestionId());
|
|
|
+ info.setSubNumber(i + 1);//每个大题下,给小题序号重新赋值
|
|
|
+ resultList.add(info);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据考试记录id判断是否开启环境检测
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean isTestDev(Long examRecordDataId) {
|
|
|
+ //用于环境检测的考试记录id
|
|
|
+ SysPropertyCacheBean examRecordDataIdObject = CacheHelper.getSysProperty("oe.testDev.examRecordDataId");
|
|
|
+ //是否开启了环境检测(请求的考试记录id等于用于环境检测的考试记录时,则认为开启了环境检测)
|
|
|
+ return examRecordDataIdObject.getHasValue() && examRecordDataId.equals(Long.valueOf(examRecordDataIdObject.getValue().toString()));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|