|
@@ -1,1686 +0,0 @@
|
|
-package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
|
|
-
|
|
|
|
-import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
|
|
|
|
-import cn.com.qmth.examcloud.api.commons.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.helper.ExamCacheTransferHelper;
|
|
|
|
-import cn.com.qmth.examcloud.core.oe.common.helper.FaceBiopsyHelper;
|
|
|
|
-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.student.bean.*;
|
|
|
|
-import cn.com.qmth.examcloud.core.oe.student.service.*;
|
|
|
|
-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.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.reports.commons.bean.OnlineExamStudentReport;
|
|
|
|
-import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
|
|
|
|
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
|
|
|
|
-import cn.com.qmth.examcloud.support.cache.bean.*;
|
|
|
|
-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.FileAnswerWebsocketCloudService;
|
|
|
|
-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 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.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");
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamStudentService examStudentService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamCloudService examCloudService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamSessionInfoService examSessionInfoService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamRecordDataService examRecordDataService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamRecordQuestionsService examRecordQuestionsService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamScoreService examScoreService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamRecordForMarkingService examRecordForMarkingService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamStudentRepo examStudentRepo;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamScoreRepo examScoreRepo;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamRecordDataRepo examRecordDataRepo;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamingRecordRepo examingRecordRepo;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- private ExamRecordPaperStructRepo examRecordPaperStructRepo;
|
|
|
|
-
|
|
|
|
- @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 ExamCaptureCloudService examCaptureCloudService;
|
|
|
|
-
|
|
|
|
- @Autowired
|
|
|
|
- FaceBiopsyService faceBiopsyService;
|
|
|
|
-
|
|
|
|
- @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;
|
|
|
|
-
|
|
|
|
- @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 originalExamStudent = examStudentRepo.findByExamStudentId(examStudentId);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("1 获取考生信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
- if (originalExamStudent == null) {
|
|
|
|
- throw new StatusException("ExamControlServiceImpl-startExam-exception", "考生不存在");
|
|
|
|
- }
|
|
|
|
- if (originalExamStudent.getStudentId().longValue() != user.getUserId().longValue()) {
|
|
|
|
- throw new StatusException("ExamControlServiceImpl-startExam-exception", "考生与当前用户不吻合");
|
|
|
|
- }
|
|
|
|
- // 检查redis session
|
|
|
|
- ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(originalExamStudent.getStudentId());
|
|
|
|
- if (examSessionInfo != null) {
|
|
|
|
- throw new StatusException("ExamControlServiceImpl-startExam-exception", "已经有考试中的科目");
|
|
|
|
- }
|
|
|
|
- // 检查并获取考试信息
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- ExamBean examBean = checkExam(originalExamStudent);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("2 检查并获取考试信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 检查并获取课程信息
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- CourseBean courseBean = checkCourse(originalExamStudent);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("3 检查并获取课程信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 获取题库试卷结构(由于存在随机抽卷,所以不能缓存 )
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
-
|
|
|
|
- // 获取题库调卷规则
|
|
|
|
- ExtractConfigCacheBean extractConfig = CacheHelper.getExtractConfig(originalExamStudent.getExamId(),
|
|
|
|
- courseBean.getCode());
|
|
|
|
- // 随机生成试卷
|
|
|
|
- Map<String, String> paperTypeMaps = getExamPaperByProbability(extractConfig.getDetails());
|
|
|
|
- if (paperTypeMaps.isEmpty()) {
|
|
|
|
- throw new StatusException("100001", "生成试卷失败");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- String paperId = paperTypeMaps.get(originalExamStudent.getPaperType());
|
|
|
|
- if (StringUtils.isEmpty(paperId)) {
|
|
|
|
- throw new StatusException("100002", "获取试卷失败");
|
|
|
|
- }
|
|
|
|
- // 生成试卷结构
|
|
|
|
- ExtractConfigPaperCacheBean extractConfigPaper = CacheHelper.getExtractConfigPaper(
|
|
|
|
- originalExamStudent.getExamId(), courseBean.getCode(), originalExamStudent.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();
|
|
|
|
-
|
|
|
|
- int examedTimes = examStudentService.updateExamStudentByStartExam(originalExamStudent.getId(),
|
|
|
|
- originalExamStudent.getIsReExamine(), originalExamStudent.getReExamineCompleted(),
|
|
|
|
- originalExamStudent.getNormalExamTimes(), examBean);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("7 更新考生信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 生成考试记录
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- ExamRecordDataEntity examRecordData = examRecordDataService.createExamRecordData(originalExamStudent, examBean,
|
|
|
|
- courseBean, paperId, examRecordPaperStruct.getId(), examedTimes, examBean.getExamTimes(),
|
|
|
|
- originalExamStudent.getIsReExamine(), extractConfigPaper.getDefaultPaper().getFullyObjective());
|
|
|
|
-
|
|
|
|
- // 生成进行中的考试记录
|
|
|
|
- Long studentId = examRecordData.getStudentId();
|
|
|
|
- examRecordDataService.createExamingRecord(examRecordData.getId(), studentId,
|
|
|
|
- examRecordData.getExamType());
|
|
|
|
-
|
|
|
|
- // 如果开启人脸比对,将同步人脸比对结果存储到抓后结果表中
|
|
|
|
- Long rootOrgId = examRecordData.getRootOrgId();
|
|
|
|
- Long examId = examRecordData.getExamId();
|
|
|
|
- Long orgId = examRecordData.getOrgId();
|
|
|
|
-
|
|
|
|
- if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
|
|
|
|
- SaveExamCaptureSyncCompareResultReq req = new SaveExamCaptureSyncCompareResultReq();
|
|
|
|
- req.setExamRecordDataId(examRecordData.getId());
|
|
|
|
- req.setStudentId(user.getUserId());
|
|
|
|
- examCaptureCloudService.saveExamCaptureSyncCompareResult(req);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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(originalExamStudent, examRecordData, examBean);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("11 创建考试会话耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("12 合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
|
- }
|
|
|
|
- // 在线考生开考打点
|
|
|
|
- ReportsUtil.report(
|
|
|
|
- new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(), examBean.getId(), examStudentId));
|
|
|
|
- return buildStartExamInfo(examRecordData.getId(), originalExamStudent, 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(examStudentEntity.getRootOrgId(), examBean.getId(), examStudentEntity.getOrgId(),
|
|
|
|
- examStudentEntity.getStudentId()));
|
|
|
|
- return startExamInfo;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 确定活体检测开始分钟数
|
|
|
|
- *
|
|
|
|
- * @param examId
|
|
|
|
- * @return
|
|
|
|
- */
|
|
|
|
- private Integer getFaceVerifyMinute(Long rootOrgId, Long examId, Long orgId, Long studentId) {
|
|
|
|
- // 如果开启了活体检测
|
|
|
|
- if (FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
|
|
|
|
- // 开始分钟数
|
|
|
|
- String startMinuteStr = ExamCacheTransferHelper
|
|
|
|
- .getCachedExamProperty(examId, studentId, 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 = ExamCacheTransferHelper
|
|
|
|
- .getCachedExamProperty(examId, studentId, 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) {
|
|
|
|
- Long examId = examStudentEntity.getExamId();
|
|
|
|
- Long studentId = examStudentEntity.getStudentId();
|
|
|
|
-
|
|
|
|
- // 学习中心特殊考试配置(是否禁考,开考时间可以特殊设置)
|
|
|
|
- ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
|
|
|
|
-
|
|
|
|
- //如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
|
|
|
|
- ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(examId,
|
|
|
|
- ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
|
|
|
|
- if (examBean.getSpecialSettingsEnabled() &&
|
|
|
|
- (limitedIfNoSpecialSettings.getHasValue() && Boolean.valueOf(limitedIfNoSpecialSettings.getValue()))) {
|
|
|
|
-
|
|
|
|
- //学生特殊设置开启未配置,不允许考试
|
|
|
|
- if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STUDENT_BASED) {
|
|
|
|
- ExamStudentSettingsCacheBean specialSettings = CacheHelper.getExamStudentSettings(examId, studentId);
|
|
|
|
- if (!specialSettings.getHasValue()) {
|
|
|
|
- throw new StatusException("100014", "考试配置未完成,不允许考试");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //机构特殊设置开启未配置,不允许考试
|
|
|
|
- if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.ORG_BASED) {
|
|
|
|
- //需求调整,所有的组织机构取学生表所关联的orgId
|
|
|
|
- Long orgId = CacheHelper.getStudent(studentId).getOrgId();
|
|
|
|
- ExamOrgSettingsCacheBean specialSettings = CacheHelper.getExamOrgSettings(examId, orgId);
|
|
|
|
- if (!specialSettings.getHasValue()) {
|
|
|
|
- throw new StatusException("100015", "考试配置未完成,不允许考试");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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.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 = ExamCacheTransferHelper
|
|
|
|
- .getCachedExamProperty(examBean.getId(), examStudent.getStudentId(),
|
|
|
|
- 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 = ExamCacheTransferHelper
|
|
|
|
- .getCachedExamProperty(examBean.getId(), examStudent.getStudentId(),
|
|
|
|
- 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 studentId 学生id
|
|
|
|
- * @return
|
|
|
|
- */
|
|
|
|
- private Long checkAndComputeExamDuration(Long studentId) {
|
|
|
|
-
|
|
|
|
- // 获取考试会话,判断考生是否已结束考试(二次校验)
|
|
|
|
- ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(studentId);
|
|
|
|
- if (examSessionInfo == null) {
|
|
|
|
- throw new StatusException("oestudent-100100", "考试会话已过期");
|
|
|
|
- }
|
|
|
|
- Long usedTime = calculateExamUsedMilliseconds(examSessionInfo);
|
|
|
|
- // 如果没有超过冻结时间,抛出异常
|
|
|
|
- if (examSessionInfo.getExamType().equals(ExamType.ONLINE.name())) {
|
|
|
|
- ExamingRecordEntity rec = GlobalHelper.getEntity(examingRecordRepo, examSessionInfo.getExamRecordDataId(),
|
|
|
|
- ExamingRecordEntity.class);
|
|
|
|
-
|
|
|
|
- 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() + "分钟后才能交卷");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return usedTime;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 计算考试已用时间(毫秒)
|
|
|
|
- *
|
|
|
|
- * @param examSessionInfo 考试会话对象
|
|
|
|
- * @return
|
|
|
|
- */
|
|
|
|
- @Override
|
|
|
|
- public Long calculateExamUsedMilliseconds(ExamSessionInfo examSessionInfo) {
|
|
|
|
- if (examSessionInfo == null) {
|
|
|
|
- throw new StatusException("oestudent-100100", "考试会话已过期");
|
|
|
|
- }
|
|
|
|
- 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;
|
|
|
|
- }
|
|
|
|
- return usedTime;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * @param examRecordDataId 考试记录id
|
|
|
|
- * @param handInExamType 交卷类型
|
|
|
|
- */
|
|
|
|
- @Override
|
|
|
|
- @Transactional
|
|
|
|
- public void handInExam(Long examRecordDataId, HandInExamType handInExamType) {
|
|
|
|
- // 此锁是为了避免自动交卷服务与断点续考交卷或活检失败交卷,同一时刻交卷争抢资源导致死锁
|
|
|
|
- String sequenceLockKey = Constants.HAND_IN_EXAM_LOCK_PREFIX + examRecordDataId;
|
|
|
|
- // 系统在请求结束后会,自动释放锁,无需手动解锁
|
|
|
|
- SequenceLockHelper.getLock(sequenceLockKey);
|
|
|
|
-
|
|
|
|
- // //如果当前考试记录状态不为考试中,则直接返回
|
|
|
|
- // if (examRecordData.getExamRecordStatus() !=
|
|
|
|
- // ExamRecordStatus.EXAM_ING) {
|
|
|
|
- // return;
|
|
|
|
- // }
|
|
|
|
- long st = System.currentTimeMillis();
|
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
-
|
|
|
|
- ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
|
|
|
|
- ExamRecordDataEntity.class);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("1 [HAND_IN_EXAM]获取考试记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Long studentId = examRecordData.getStudentId();
|
|
|
|
- if (handInExamType == HandInExamType.MANUAL) {
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
-
|
|
|
|
- // 得到考试时长,校验是否达到冻结时间
|
|
|
|
- long usedExamTime = checkAndComputeExamDuration(studentId);
|
|
|
|
- examRecordData.setUsedExamTime(usedExamTime);
|
|
|
|
- examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
|
|
|
|
- examRecordData.setEndTime(new Date());
|
|
|
|
-
|
|
|
|
- // 手工手卷时,如果开启人脸检测,则更新抓拍队列优先级
|
|
|
|
- Long rootOrgId = examRecordData.getRootOrgId();
|
|
|
|
- Long examId = examRecordData.getExamId();
|
|
|
|
-
|
|
|
|
- if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
|
|
|
|
- // 更新照片抓拍队列优先级为高优先级
|
|
|
|
- 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.getId(),
|
|
|
|
- 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(examRecordData);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("3 [HAND_IN_EXAM]计算保存考试分数耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- // 把进行中的考试记录放入交卷队列中
|
|
|
|
- examRecordDataService.createHandInExamRecord(examRecordData.getId(), studentId);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("4 [HAND_IN_EXAM]把进行中的考试记录放入交卷队列中耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- // 删除进行中的考试记录数据
|
|
|
|
- examRecordDataService.deleteExamingRecord(examRecordData.getId());
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("5 [HAND_IN_EXAM]删除进行中的考试记录数据耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- // 删除redis会话
|
|
|
|
- examSessionInfoService.deleteExamSessionInfo(studentId);
|
|
|
|
- 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();
|
|
|
|
- // 计算活体检测结果(根据已检测出照片数据更新考试记录中的相关人脸检测的统计字段)
|
|
|
|
- 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.getId());
|
|
|
|
-
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void cleanTempFileAnswers() {
|
|
|
|
- // 默认删除1天以前的数据
|
|
|
|
- int days = PropertyHolder.getInt("oe.cleanTempFileAnswer.thresholdDays", 1);
|
|
|
|
- Date dateBeforeDays = DateUtils.addDays(new Date(), -1 * days);
|
|
|
|
- examFileAnswerTempRepo.deleteByCreationTime(dateBeforeDays);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 考试心跳每分钟调用一次
|
|
|
|
- *
|
|
|
|
- * @param user 学生
|
|
|
|
- */
|
|
|
|
- @Override
|
|
|
|
- public long examHeartbeat(User user) {
|
|
|
|
- Long studentId = user.getUserId();
|
|
|
|
- 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);
|
|
|
|
- // 会话过期时间:秒
|
|
|
|
- int expireTime = (examSessionInfo.getExamReconnectTime() + 1) * 60;
|
|
|
|
- // 更新考试会话过期时间
|
|
|
|
- examSessionInfoService.saveExamSessionInfo(studentId, examSessionInfo, expireTime);
|
|
|
|
- // 在线考生心跳打点
|
|
|
|
- ReportsUtil.report(new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(),
|
|
|
|
- examSessionInfo.getExamId(), examSessionInfo.getExamStudentId()));
|
|
|
|
- // 返回考试剩余时间 :考试时长-心跳次数*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());
|
|
|
|
-
|
|
|
|
- // 断点续考时重新计算活体检测的分钟数
|
|
|
|
- Integer faceVerifyMinute = null;
|
|
|
|
- FaceBiopsyScheme faceBiopsyScheme = FaceBiopsyHelper.getFaceBiopsyScheme(examStudentEntity.getRootOrgId());
|
|
|
|
-
|
|
|
|
- // 如果是新活体检测方案,则使用新的计算方案计算活检开始时间
|
|
|
|
- if (faceBiopsyScheme == FaceBiopsyScheme.NEW) {
|
|
|
|
- faceVerifyMinute = faceBiopsyService.calculateFaceBiopsyStartMinute(examingRecord.getExamRecordDataId());
|
|
|
|
- }
|
|
|
|
- // 非新活检,默认使用旧的活检计算方式
|
|
|
|
- else {
|
|
|
|
- faceVerifyMinute = examFaceLivenessVerifyService.getFaceLivenessVerifyMinute(
|
|
|
|
- examStudentEntity.getRootOrgId(), examStudentEntity.getOrgId(),
|
|
|
|
- examStudentEntity.getExamId(), studentId,
|
|
|
|
- examingRecord.getExamRecordDataId(), examSessionInfo.getHeartbeat());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- checkExamInProgressInfo.setFaceVerifyMinute(faceVerifyMinute);
|
|
|
|
- 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) {
|
|
|
|
- delayHandInExamIfLocked(examingRecord.getExamRecordDataId());
|
|
|
|
- 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);
|
|
|
|
- delayHandInExamIfLocked(examingRecord.getExamRecordDataId());
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
- return examingRecord;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 如果有序列化锁,则延迟交卷
|
|
|
|
- *
|
|
|
|
- * @param examRecordDataId 考试记录id
|
|
|
|
- * @return
|
|
|
|
- */
|
|
|
|
- private void delayHandInExamIfLocked(Long examRecordDataId) {
|
|
|
|
- try {
|
|
|
|
- handInExam(examRecordDataId, HandInExamType.AUTO);
|
|
|
|
- } catch (SequenceLockException e) {
|
|
|
|
- // 如果发现自动服务正在交卷,则重试1500毫秒获取考试记录状态,判断是否已交卷
|
|
|
|
- int loopTimes = 0;
|
|
|
|
- while (loopTimes <= 15) {
|
|
|
|
- loopTimes++;
|
|
|
|
- ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
|
|
|
|
- ExamRecordDataEntity.class);
|
|
|
|
-
|
|
|
|
- // 1500毫秒内如果交卷成功,则退出循环
|
|
|
|
- if (examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_ING) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- Util.sleep(TimeUnit.MILLISECONDS, 100);
|
|
|
|
- }
|
|
|
|
- throw e;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void cleanExamingRecord(ExamingRecordEntity examingRecord) {
|
|
|
|
-
|
|
|
|
- // 只有考试会话结束的考试才执行清理动作
|
|
|
|
- ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(examingRecord.getStudentId());
|
|
|
|
- if (examSessionInfo != null) {
|
|
|
|
- cleanExamRecordTaskLog
|
|
|
|
- .debug("[CLEAN_EXAMING_RECORD_" + examingRecord.getExamRecordDataId() + "]考试未结束,不执行清理.");
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 只有考试状态为考试中,系统才需要执行交卷动作,因为有可能已经手动交卷,所以这里再做一次判断
|
|
|
|
- // if (examRecordData.getExamRecordStatus() ==
|
|
|
|
- // ExamRecordStatus.EXAM_ING) {
|
|
|
|
- cleanExamRecordTaskLog
|
|
|
|
- .debug("[CLEAN_EXAMING_RECORD_" + examingRecord.getExamRecordDataId() + "]状态为进行中考试,执行交卷[handInExam],.");
|
|
|
|
- handInExam(examingRecord.getExamRecordDataId(), HandInExamType.AUTO);
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- cleanExamRecordTaskLog.debug("[CLEAN_EXAMING_RECORD_" + examingRecord.getExamRecordDataId() + "]考试记录交卷完成.");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void cleanHandInExamRecord(HandInExamRecordEntity handInExamRecord) {
|
|
|
|
- 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(), examRecordData.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);
|
|
|
|
- // 发送获取分数通知
|
|
|
|
- examScoreObtainQueueService.sendObtainScoreNotify(examRecordData.getRootOrgId());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public UpyunSignatureInfo getUpyunSignature(GetUpyunSignatureReq req) {
|
|
|
|
- UpyunSignatureInfo u = new UpyunSignatureInfo();
|
|
|
|
- try {
|
|
|
|
- ExamRecordDataEntity examRecordData = 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();
|
|
|
|
-
|
|
|
|
- filePath.append(SEPARATOR).append(OE_ANSWER_FILE_PATH).append(SEPARATOR)
|
|
|
|
- .append(examRecordData.getExamStudentId()).append(SEPARATOR).append(req.getExamRecordDataId())
|
|
|
|
- .append(SEPARATOR).append(req.getOrder()).append(SEPARATOR)
|
|
|
|
- .append(examRecordData.getExamStudentId()).append(UNDERLINE).append(req.getExamRecordDataId())
|
|
|
|
- .append(UNDERLINE).append(req.getOrder()).append(UNDERLINE).append(System.currentTimeMillis())
|
|
|
|
- .append(RandomUtils.nextInt(8999) + 1000);
|
|
|
|
-
|
|
|
|
- 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);
|
|
|
|
- req.setExamStudentId(examRecordData.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) {
|
|
|
|
- long st = System.currentTimeMillis();
|
|
|
|
-
|
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
- 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", "无效的二维码");
|
|
|
|
- }
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("1 签名校验耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- // 判断考生是否存在
|
|
|
|
- ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(Long.valueOf(examStudentId));
|
|
|
|
- if (examStudentEntity == null) {
|
|
|
|
- throw new StatusException("100012", "考生不存在");
|
|
|
|
- }
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("2 考生是否存在校验耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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());
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- ExamRecordQuestionsEntity examRecordQuestionsEntity = examRecordQuestionsService
|
|
|
|
- .getExamRecordQuestionsAndFixExamRecordDataIfNecessary(Long.valueOf(examRecordDataId));
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("3 获取作答记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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)));
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- try {
|
|
|
|
- this.sendScanQrCodeToWebSocket(clientId, Long.valueOf(examRecordDataId), Integer.valueOf(order));
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- throw new StatusException("100011", "消息通知失败", e);
|
|
|
|
- }
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("4 发websocket耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("5 合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
|
- }
|
|
|
|
- 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) {
|
|
|
|
- long st = System.currentTimeMillis();
|
|
|
|
-
|
|
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
- // 判断考试是否结束
|
|
|
|
- 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", "考试已结束");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("1 判断考试是否结束耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- startTime = System.currentTimeMillis();
|
|
|
|
- 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());
|
|
|
|
- ExamFileAnswerTempEntity result = examFileAnswerTempRepo.save(et);
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("2 保存文件临时作答记录耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (log.isDebugEnabled()) {
|
|
|
|
- log.debug("3 合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
|
|
|
|
- }
|
|
|
|
- return result;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @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()));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-}
|
|
|