Browse Source

完善交卷相关代码

lideyin 5 years ago
parent
commit
00491b8ad3

+ 0 - 68
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/enums/ExamProperties.java

@@ -1,68 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.student.base.enums;
-
-/**
- * 
- * @author  	chenken
- * @date    	2018年11月15日 下午5:37:35
- * @company 	QMTH
- * @description 考试属性
- */
-public enum ExamProperties {
-
-	SCORE_PUBLISHING("发布成绩"),
-	IS_ENTRANCE_EXAM("是否入学考试"),
-	FREEZE_TIME("交卷冻结时间"),
-	EXAM_RECONNECT_TIME("断点续考时间"),
-	BEFORE_EXAM_REMARK("考前说明"),
-	AFTER_EXAM_REMARK("考后说明"),
-	SHOW_CHEATING_REMARK("是否展示作弊"),
-	CHEATING_REMARK("作弊说明"),
-	PRACTICE_TYPE("练习模式"),
-	SINGLE_EDIT("单选题补充说明是否可填"),
-	MUTIPLE_EDIT("多选题补充说明是否可填"),
-	BOOL_EDIT("判断题补充说明是否可填"),
-	FILL_BLANK_EDIT("填空题补充说明是否可填"),
-	SINGLE_ANSWER_REMARK("单选题补充说明"),
-	MUTIPLE_ANSWER_REMARK("多选题补充说明"),
-	BOOL_ANSWER_REMARK("判断题补充说明"),
-	FILL_BLANK_REMARK("填空题补充说明"),
-	TEXT_ANSWER_REMARK("问答题补充说明"),
-	nestedAnswerRemark("套题补充说明"),
-	IS_FACE_ENABLE("是否启用人脸识别"),
-	IS_FACE_CHECK("进入考试是否验证人脸识别"),
-	WARN_THRESHOLD("人脸检测预警阈值"),
-	MARKING_TYPE("阅卷方式"),
-	IS_FACE_VERIFY("是否开启人脸活体检测"),
-	FACE_VERIFY_START_MINUTE("活体检测开始分钟数"),
-	FACE_VERIFY_END_MINUTE("活体检测结束分钟数"),
-	ADD_FACE_VERIFY_OUT_FREEZE_TIME("冻结时间外新加人脸活体检测"),
-	OUT_FREEZE_TIME_FACE_VERIFY_START_MINUTE("冻结时间外活体检测开始分钟数"),
-	OUT_FREEZE_TIME_FACE_VERIFY_END_MINUTE("冻结时间外活体检测结束分钟数"),
-	IP_LIMIT("是否IP限制"),
-	IP_ADDRESSES("IP白名单"),
-	IS_OBJ_SCORE_VIEW("是否显示客观题成绩"),
-	CAN_UPLOAD_ATTACHMENT("是否允许上传附件(离线考试)"),
-	LIVING_WARN_THRESHOLD("人脸真实性阈值"),
-	MARKING_TASK_BUILDED("阅卷是否生成评卷任务"),
-	OFFLINE_UPLOAD_FILE_TYPE("离线考试上传文件类型"),
-	PUSH_SCORE("是否推送分数"),
-	MAX_INTERRUPT_NUM("最大断点续考次数"),
-	IS_STRANGER_ENABLE("是否启用陌生人检测"),
-	LIMITED_IF_NO_SPECIAL_SETTINGS("无特殊设置时禁止考试"),
-	WEIXIN_ANSWER_ENABLED("是否开放微信小程序作答");
-	
-	private ExamProperties(String desc){
-		this.desc = desc;
-	}
-	
-	private String desc;
-
-	public String getDesc() {
-		return desc;
-	}
-
-	public void setDesc(String desc) {
-		this.desc = desc;
-	}
-	
-}

+ 0 - 255
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/helper/ExamCacheTransferHelper.java

@@ -1,255 +0,0 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-29 10:44:05.
- * *************************************************
- */
-
-package cn.com.qmth.examcloud.core.oe.student.base.helper;
-
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-
-import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
-import cn.com.qmth.examcloud.commons.util.StringUtil;
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
-import cn.com.qmth.examcloud.core.oe.student.base.enums.ExamProperties;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.OrgPropertyCacheBean;
-import io.swagger.annotations.ApiOperation;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
-/**
- * @Description 网考缓存实体转换服务
- * @Author lideyin
- * @Date 2019/8/13 16:00
- * @Version 1.0
- */
-public class ExamCacheTransferHelper {
-
-    /**
-     * 获取缓存的考试信息
-     *
-     * @param examId    考试id
-     * @param studentId 学生id
-     * @return
-     */
-    public static ExamBean getCachedExam(Long examId, Long studentId) {
-        ExamSettingsCacheBean examCacheBean = CacheHelper.getExamSettings(examId);
-        //默认取考试中的通用设置
-        ExamBean examBean = copyExamBeanFrom(examCacheBean);
-
-        //是否开启特殊设置
-        Boolean specialSettingsEnabled = examCacheBean.getSpecialSettingsEnabled();
-        if (specialSettingsEnabled == true) {
-            ExamSpecialSettingsType specialSettingsType = examCacheBean.getSpecialSettingsType();
-
-            //开启特殊设置却未进行任何设置则直接返回通用设置
-            if (null == specialSettingsType) {
-                return examBean;
-            }
-
-            switch (specialSettingsType) {
-                case ORG_BASED:
-                    //需求调整,所有的组织机构取学生表所关联的orgId
-                    Long orgId = CacheHelper.getStudent(studentId).getOrgId();
-                    initOrgSpecialSettings(examId, orgId, examBean);
-                    break;
-                case STUDENT_BASED:
-                    //初始化学生的特殊化设置
-                    initStudentSpecialSettings(examId, studentId, examBean);
-                    break;
-                case COURSE_BASED:
-                    //暂无此需求
-                    throw new NotImplementedException();
-            }
-        }
-        return examBean;
-    }
-
-    /**
-     * 获取默认(即非个性化)的考试信息
-     * <b>注意:只有非个性化的信息才可调用此方法</b>
-     *
-     * @param examId 考试id
-     * @return
-     */
-    public static ExamBean getDefaultCachedExam(Long examId) {
-        return copyExamBeanFrom(CacheHelper.getExamSettings(examId));
-    }
-
-    /**
-     * 获取考试的属性
-     *
-     * @param examId
-     * @param studentId
-     * @param propKey
-     * @return
-     */
-    public static ExamPropertyCacheBean getCachedExamProperty(Long examId, Long studentId, String propKey) {
-        ExamSettingsCacheBean examCacheBean = CacheHelper.getExamSettings(examId);
-        //默认取考试中的通用设置
-        ExamPropertyCacheBean examPropertyCacheBean = CacheHelper.getExamProperty(examId, propKey);
-
-        //是否开启特殊设置
-        Boolean specialSettingsEnabled = examCacheBean.getSpecialSettingsEnabled();
-        if (specialSettingsEnabled == true) {
-            ExamSpecialSettingsType specialSettingsType = examCacheBean.getSpecialSettingsType();
-
-            //开启特殊设置却未进行任何设置则直接返回通用设置
-            if (null == specialSettingsType) {
-                return examPropertyCacheBean;
-            }
-
-            ExamPropertyCacheBean specialExamProperty = null;
-            switch (specialSettingsType) {
-                case ORG_BASED:
-                    //需求调整,所有的组织机构取学生表所关联的orgId
-                    Long orgId = CacheHelper.getStudent(studentId).getOrgId();
-                    specialExamProperty = CacheHelper.getExamOrgProperty(examId, orgId, propKey);
-                    break;
-                case STUDENT_BASED:
-                    specialExamProperty = CacheHelper.getExamStudentProperty(examId, studentId, propKey);
-                    break;
-                case COURSE_BASED:
-                    //暂无此需求
-                    throw new NotImplementedException();
-            }
-            if (specialExamProperty.getHasValue()) {
-                return specialExamProperty;
-            }
-        }
-
-        return examPropertyCacheBean;
-    }
-
-    /**
-     * 获取默认(即非个性化)的考试属性信息
-     *
-     * @param examId
-     * @param propKey
-     * @return
-     */
-    public static ExamPropertyCacheBean getDefaultCachedExamProperty(Long examId, String propKey) {
-        return CacheHelper.getExamProperty(examId, propKey);
-    }
-
-    /**
-     * 获取课程
-     *
-     * @param courseId 课程id
-     * @return CourseBean
-     */
-    public static CourseBean getCachedCourse(Long courseId) {
-        CourseCacheBean courseCacheBean = CacheHelper.getCourse(courseId);
-        return copyCourseBeanFrom(courseCacheBean);
-    }
-
-    @ApiOperation(value = "是否可以微信作答", notes = "")
-    @GetMapping("weixinAnswerEnabled/{examId}")
-    public static Boolean weixinAnswerEnabled(@PathVariable Long examId) {
-
-        ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
-
-        OrgPropertyCacheBean orgConf = CacheHelper.getOrgProperty(examSettings.getRootOrgId(),
-                ExamProperties.WEIXIN_ANSWER_ENABLED.name());
-        ExamPropertyCacheBean examConf = CacheHelper.getExamProperty(examId,
-                ExamProperties.WEIXIN_ANSWER_ENABLED.name());
-
-        String orgValue = orgConf.getValue();
-        String examValue = examConf.getValue();
-
-        if (!orgConf.getHasValue()) {
-            return false;
-        }
-        if (StringUtils.isBlank(orgValue)) {
-            return false;
-        }
-
-        if (StringUtils.isBlank(examValue)) {
-            return false;
-        }
-
-        if (StringUtil.isTrue(orgValue) && StringUtil.isTrue(examValue)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * 初始化学生的特殊化设置
-     *
-     * @param examId
-     * @param studentId
-     * @param examBean
-     */
-    private static void initStudentSpecialSettings(Long examId, Long studentId, ExamBean examBean) {
-        ExamSettingsCacheBean examSpecialSetting =
-                CacheHelper.getExamStudentSettings(examId, studentId);
-        if (null != examSpecialSetting.getBeginTime()) {
-            examBean.setBeginTime(examSpecialSetting.getBeginTime());
-        }
-        if (null != examSpecialSetting.getEndTime()) {
-            examBean.setEndTime(examSpecialSetting.getEndTime());
-        }
-    }
-
-    /**
-     * 初始化组织机构的特殊化设置
-     *
-     * @param examId
-     * @param orgId
-     * @param examBean
-     */
-    private static void initOrgSpecialSettings(Long examId, Long orgId, ExamBean examBean) {
-        ExamSettingsCacheBean examSpecialSetting =
-                CacheHelper.getExamOrgSettings(examId, orgId);
-        if (null != examSpecialSetting.getBeginTime()) {
-            examBean.setBeginTime(examSpecialSetting.getBeginTime());
-        }
-        if (null != examSpecialSetting.getEndTime()) {
-            examBean.setEndTime(examSpecialSetting.getEndTime());
-        }
-
-        if (null != examSpecialSetting.getExamLimit()) {
-            examBean.setExamLimit(examSpecialSetting.getExamLimit());
-        }
-    }
-
-    private static ExamBean copyExamBeanFrom(ExamSettingsCacheBean examCacheBean) {
-        ExamBean resultBean = new ExamBean();
-        resultBean.setId(examCacheBean.getId());
-        resultBean.setBeginTime(examCacheBean.getBeginTime());
-        resultBean.setCode(examCacheBean.getCode());
-        resultBean.setDuration(examCacheBean.getDuration());
-        resultBean.setEnable(examCacheBean.getEnable());
-        resultBean.setEndTime(examCacheBean.getEndTime());
-        resultBean.setExamLimit(examCacheBean.getExamLimit());
-        resultBean.setExamTimes(examCacheBean.getExamTimes());
-        resultBean.setExamType(examCacheBean.getExamType());
-        resultBean.setName(examCacheBean.getName());
-        resultBean.setRemark(examCacheBean.getRemark());
-        resultBean.setRootOrgId(examCacheBean.getRootOrgId());
-        resultBean.setSpecialSettingsEnabled(examCacheBean.getSpecialSettingsEnabled());
-        resultBean.setSpecialSettingsType(examCacheBean.getSpecialSettingsType());
-        return resultBean;
-    }
-
-    private static CourseBean copyCourseBeanFrom(CourseCacheBean courseCacheBean) {
-        CourseBean resultBean = new CourseBean();
-        resultBean.setCode(courseCacheBean.getCode());
-        resultBean.setEnable(courseCacheBean.getEnable());
-        resultBean.setId(courseCacheBean.getId());
-        resultBean.setLevel(courseCacheBean.getLevel());
-        resultBean.setName(courseCacheBean.getName());
-        resultBean.setRootOrgId(courseCacheBean.getRootOrgId());
-
-        return resultBean;
-    }
-}

+ 13 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamRecordDataEntity.java

@@ -95,6 +95,11 @@ public class ExamRecordDataEntity extends JpaEntity {
      */
     private Date endTime;
 
+    /**
+     * 考试被清理时间
+     */
+    private Date cleanTime;
+
     /**
      * 考试时长
      */
@@ -329,4 +334,12 @@ public class ExamRecordDataEntity extends JpaEntity {
     public void setFaceVerifyResult(IsSuccess faceVerifyResult) {
         this.faceVerifyResult = faceVerifyResult;
     }
+
+    public Date getCleanTime() {
+        return cleanTime;
+    }
+
+    public void setCleanTime(Date cleanTime) {
+        this.cleanTime = cleanTime;
+    }
 }

+ 4 - 2
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamRecordDataService.java

@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.core.oe.student.service;
 
 import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 import cn.com.qmth.examcloud.support.examing.ExamingSession;
 
@@ -26,8 +28,8 @@ public interface ExamRecordDataService {
      *            是否全客观题
      * @return
      */
-    public ExamRecordData createExamRecordData(ExamingSession examingSession, ExamBean examBean, CourseBean courseBean,
-            String basePaperId);
+    public ExamRecordData createExamRecordData(ExamingSession examingSession, ExamSettingsCacheBean examBean, CourseCacheBean courseBean,
+                                               String basePaperId);
     
     /**
      * 保存

+ 104 - 52
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/impl/ExamControlServiceImpl.java

@@ -1,48 +1,21 @@
 package cn.com.qmth.examcloud.core.oe.student.service.impl;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
 import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.UUID;
 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.student.base.enums.ExamProperties;
-import cn.com.qmth.examcloud.core.oe.student.base.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.core.oe.student.base.utils.CommonUtil;
 import cn.com.qmth.examcloud.core.oe.student.base.utils.QuestionTypeUtil;
 import cn.com.qmth.examcloud.core.oe.student.bean.CheckExamInProgressInfo;
 import cn.com.qmth.examcloud.core.oe.student.bean.ExamRecordPaperStruct;
 import cn.com.qmth.examcloud.core.oe.student.bean.StartExamInfo;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamFaceLivenessVerifyService;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordPaperStructService;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordQuestionsService;
-import cn.com.qmth.examcloud.core.oe.student.service.ExamingSessionService;
-import cn.com.qmth.examcloud.core.oe.student.service.FaceBiopsyService;
+import cn.com.qmth.examcloud.core.oe.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.core.oe.task.api.request.UpdateExamCaptureQueuePriorityReq;
 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;
@@ -52,22 +25,30 @@ import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrap
 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.Constants;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.ExamOrgSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamStudentSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigDetailCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigPaperCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.*;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
 import cn.com.qmth.examcloud.support.enums.HandInExamType;
 import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 import cn.com.qmth.examcloud.support.examing.ExamingSession;
 import cn.com.qmth.examcloud.support.examing.ExamingStatus;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
 import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author chenken
@@ -97,10 +78,10 @@ public class ExamControlServiceImpl implements ExamControlService {
 
     @Autowired
     private ExamCloudService examCloudService;
-
+    
     @Autowired
     private FaceBiopsyService faceBiopsyService;
-
+    
     @Autowired
     private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
 
@@ -133,14 +114,14 @@ public class ExamControlServiceImpl implements ExamControlService {
 
         // 检查并获取考试信息
         startTime = System.currentTimeMillis();
-        ExamBean examBean = checkExam(examingSession.getExamId(), examingSession.getStudentId());
+        ExamSettingsCacheBean examBean = checkExam(examingSession.getExamId(), examingSession.getStudentId());
         if (log.isDebugEnabled()) {
             log.debug("2 检查并获取考试信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
         }
 
         // 检查并获取课程信息
         startTime = System.currentTimeMillis();
-        CourseBean courseBean = checkCourse(examingSession.getCourseId());
+        CourseCacheBean courseBean = checkCourse(examingSession.getCourseId());
         if (log.isDebugEnabled()) {
             log.debug("3 检查并获取课程信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
         }
@@ -234,14 +215,54 @@ public class ExamControlServiceImpl implements ExamControlService {
     /**
      * 交卷
      *
-     * @param examRecordDataId
-     *            考试记录id
-     * @param handInExamType
-     *            交卷类型
+     * @param examRecordDataId 考试记录id
+     * @param handInExamType   交卷类型
      */
+    @Transactional
     @Override
     public void handInExam(Long examRecordDataId, HandInExamType handInExamType) {
+        // 此锁是为了避免自动交卷服务与断点续考交卷或活检失败交卷,同一时刻交卷争抢资源导致死锁
+        String sequenceLockKey = Constants.HAND_IN_EXAM_LOCK_PREFIX + examRecordDataId;
+        // 系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+
+        Long studentId = examRecordData.getStudentId();
+        if (handInExamType == HandInExamType.MANUAL) {
+
+            // 得到考试时长,校验是否达到冻结时间
+            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)) {
+                // 更新照片抓拍队列优先级为高优先级
+                UpdateExamCaptureQueuePriorityReq req =new UpdateExamCaptureQueuePriorityReq();
+                req.setExamRecordDataId(examRecordDataId);
+                req.setPriority(Constants.PROCESS_CAPTURE_HIGH_PRIORITY);
+                examCaptureCloudService.updateExamCaptureQueuePriority(req);
+            }
+        } else if (handInExamType == HandInExamType.AUTO) {
+            examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_AUTO_HAND_IN);
+            examRecordData.setCleanTime(new Date());
+        } else {
+            throw new StatusException("201101", "暂不支持的交卷类型");
+        }
+
+        //标记当前状态为手动交卷
+        examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
 
+        // 保存考试记录
+        examRecordDataService.saveExamRecordDataCache(examRecordDataId,examRecordData);
+
+        // 删除redis会话
+        examingSessionService.deleteExamingSession(studentId);
     }
 
     /**
@@ -252,10 +273,10 @@ public class ExamControlServiceImpl implements ExamControlService {
      * @param studentId
      * @return
      */
-    private ExamBean checkExam(Long examId, Long studentId) {
+    private ExamSettingsCacheBean checkExam(Long examId, Long studentId) {
 
         // 学习中心特殊考试配置(是否禁考,开考时间可以特殊设置)
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
         // 如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
         ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(examId,
                 ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
@@ -294,8 +315,8 @@ public class ExamControlServiceImpl implements ExamControlService {
         return examBean;
     }
 
-    private CourseBean checkCourse(Long courseId) {
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+    private CourseCacheBean checkCourse(Long courseId) {
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
         if (!courseBean.getEnable()) {
             throw new StatusException("3001", "该课程已被禁用");
         }
@@ -447,8 +468,8 @@ public class ExamControlServiceImpl implements ExamControlService {
         return false;
     }
 
-    private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession, ExamBean examBean,
-            CourseBean courseBean) {
+    private StartExamInfo buildStartExamInfo(Long examRecordDataId, ExamingSession examingSession, ExamSettingsCacheBean examBean,
+                                             CourseCacheBean courseBean) {
         StartExamInfo startExamInfo = new StartExamInfo();
         startExamInfo.setExamRecordDataId(examRecordDataId);
         startExamInfo.setCourseName(courseBean.getName());
@@ -495,7 +516,7 @@ public class ExamControlServiceImpl implements ExamControlService {
      * @param examBean
      */
     public void initializeExamRecordSession(ExamingSession examSessionInfo, ExamRecordData examRecordData,
-            final ExamBean examBean) {
+            final ExamSettingsCacheBean examBean) {
         examSessionInfo.setExamRecordDataId(examRecordData.getId());
         examSessionInfo.setStartTime(examRecordData.getStartTime().getTime());
         examSessionInfo.setExamType(examBean.getExamType());
@@ -675,4 +696,35 @@ public class ExamControlServiceImpl implements ExamControlService {
         // 返回考试剩余时间
         return examSessionInfo.getExamDuration() - (examSessionInfo.getCost() * 1000);
     }
+
+    /**
+     * 计算考试时长 校验是否达到冻结时间
+     *
+     * @param studentId 学生id
+     * @return
+     */
+    private Long checkAndComputeExamDuration(Long studentId) {
+
+        // 获取考试会话,判断考生是否已结束考试(二次校验)
+        ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
+        if (examingSession == null) {
+            throw new StatusException("oestudent-100100", "考试会话已过期");
+        }
+        Long examUsedMilliSeconds = examingSession.getCost() * 1000;
+        // 如果没有超过冻结时间,抛出异常
+        if (examingSession.getExamType().equals(ExamType.ONLINE.name())) {
+            ExamRecordData examRecordData  = examRecordDataService.getExamRecordDataCache(examingSession.getExamRecordDataId());
+
+            if (examRecordData != null && examRecordData.getIsExceed() != null && examRecordData.getIsExceed()) {// 超过断点最大次数的不校验冻结时间
+                return examUsedMilliSeconds;
+            }
+            long freezeTime = examingSession.getFreezeTime() * 60 * 1000;
+            if (examUsedMilliSeconds < freezeTime) {
+                throw new StatusException("ExamControlServiceImpl-checkAndComputeExamDuration-exception",
+                        "开考" + examingSession.getFreezeTime() + "分钟后才能交卷");
+            }
+        }
+
+        return examUsedMilliSeconds;
+    }
 }

+ 4 - 2
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/impl/ExamRecordDataServiceImpl.java

@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.core.oe.student.service.impl;
 
 import java.util.Date;
 
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -36,8 +38,8 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
     @Transactional
     @Override
-    public ExamRecordData createExamRecordData(ExamingSession examingSession, ExamBean examBean,
-            CourseBean courseBean, String basePaperId) {
+    public ExamRecordData createExamRecordData(ExamingSession examingSession, ExamSettingsCacheBean examBean,
+                                               CourseCacheBean courseBean, String basePaperId) {
         ExamRecordDataEntity examRecordData = new ExamRecordDataEntity();
         examRecordData.setExamId(examBean.getId());
         examRecordData.setExamType(ExamType.valueOf(examBean.getExamType()));