فهرست منبع

系统通知,监考待审/已审,考试计时等功能开发

lideyin 4 سال پیش
والد
کامیت
53bc3b3072

+ 17 - 0
examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamControlController.java

@@ -31,6 +31,7 @@ import com.google.common.collect.Maps;
 import com.mysql.cj.util.StringUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import org.apache.commons.lang.math.RandomUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -81,6 +82,22 @@ public class ExamControlController extends ControllerSupport {
         return startExamInfo;
     }
 
+    /**
+     * 开始答题
+     */
+    @ApiOperation(value = "开始答题")
+    @GetMapping("/startAnswer")
+    public void startAnswer(@RequestParam @ApiParam(value = "考试记录id") Long examRecordDataId) {
+        User user = getAccessUser();
+        String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
+        StartExamInfo startExamInfo;
+        // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
+        examControlService.startAnswer(examRecordDataId);
+    }
+
     /**
      * 断点续考:检查正在进行中的考试
      */

+ 7 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamContinuedRecordRepo.java

@@ -24,4 +24,11 @@ public interface ExamContinuedRecordRepo
      */
     List<ExamContinuedRecordEntity> findByExamRecordDataId(Long examRecordDataId);
 
+    /**
+     * 查找最新的断点续考记录
+     * @param examRecordDataId
+     * @return
+     */
+    ExamContinuedRecordEntity findTopByExamRecordDataIdOrderByIdDesc(Long examRecordDataId);
+
 }

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

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.core.oe.student.dao.entity;
 
 import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
 import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
 
 import javax.persistence.Entity;
 import javax.persistence.Index;
@@ -19,6 +20,7 @@ import java.util.Date;
         @Index(name = "IDX_E_O_E_C_R_001", columnList = "examRecordDataId"),
 })
 @DynamicInsert
+@DynamicUpdate
 public class ExamContinuedRecordEntity extends WithIdJpaEntity {
     private static final long serialVersionUID = 1559727365523696406L;
 

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

@@ -20,6 +20,7 @@ import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.support.enums.HandInExamType;
 import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+import org.hibernate.annotations.DynamicUpdate;
 
 /**
  * @author chenken
@@ -36,6 +37,7 @@ import cn.com.qmth.examcloud.web.jpa.JpaEntity;
         @Index(name = "IDX_E_O_E_R_D_005", columnList = "batchNum"),
 })
 @DynamicInsert
+@DynamicUpdate
 public class ExamRecordDataEntity extends JpaEntity {
     /**
      *
@@ -239,6 +241,21 @@ public class ExamRecordDataEntity extends JpaEntity {
      */
     private Integer examStageOrder;
 
+    /**
+     * 学生最后活动时间
+     */
+    private Date lastActiveTime;
+
+    /**
+     * 断点续考时间
+     */
+    private Date continuedTime;
+
+    /**
+     * 进入考试时间
+     */
+    private Date enterExamTime;
+
     public Long getId() {
         return id;
     }
@@ -565,4 +582,28 @@ public class ExamRecordDataEntity extends JpaEntity {
     public void setExamStageOrder(Integer examStageOrder) {
         this.examStageOrder = examStageOrder;
     }
+
+    public Date getLastActiveTime() {
+        return lastActiveTime;
+    }
+
+    public void setLastActiveTime(Date lastActiveTime) {
+        this.lastActiveTime = lastActiveTime;
+    }
+
+    public Date getContinuedTime() {
+        return continuedTime;
+    }
+
+    public void setContinuedTime(Date continuedTime) {
+        this.continuedTime = continuedTime;
+    }
+
+    public Date getEnterExamTime() {
+        return enterExamTime;
+    }
+
+    public void setEnterExamTime(Date enterExamTime) {
+        this.enterExamTime = enterExamTime;
+    }
 }

+ 7 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamControlService.java

@@ -21,6 +21,13 @@ public interface ExamControlService {
      */
     StartExamInfo startExam(Long examStudentId, User user);
 
+    /**
+     * 开始答题
+     *
+     * @param examRecordDataId
+     */
+    void startAnswer(Long examRecordDataId);
+
     /**
      * 交卷
      *

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

@@ -27,6 +27,10 @@ import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordQuestions
 import cn.com.qmth.examcloud.core.oe.student.base.utils.CommonUtil;
 import cn.com.qmth.examcloud.core.oe.student.base.utils.QuestionTypeUtil;
 import cn.com.qmth.examcloud.core.oe.student.bean.*;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamContinuedRecordRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamContinuedRecordEntity;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.student.service.*;
 import cn.com.qmth.examcloud.core.oe.task.api.ExamCaptureCloudService;
 import cn.com.qmth.examcloud.core.oe.task.api.request.SaveExamCaptureSyncCompareResultReq;
@@ -56,6 +60,7 @@ import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
 import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
 import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
 import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.ws.api.WsCloudService;
@@ -136,6 +141,10 @@ public class ExamControlServiceImpl implements ExamControlService {
     SyncExamDataCloudService syncExamDataCloudService;
     @Autowired
     private ExamStageCloudService examStageCloudService;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamContinuedRecordRepo examContinuedRecordRepo;
 
     private static final String SEPARATOR = "/";
 
@@ -173,7 +182,8 @@ public class ExamControlServiceImpl implements ExamControlService {
 
         // 检查并获取考试信息
         startTime = System.currentTimeMillis();
-        ExamSettingsCacheBean examBean = checkExam(examingSession.getExamId(), examingSession.getStudentId());
+        ExamSettingsCacheBean examBean = checkExam(examingSession.getExamId(),
+                examingSession.getStudentId(), examingSession.getExamStageId());
         if (log.isDebugEnabled()) {
             log.debug("2 检查并获取考试信息耗时:" + (System.currentTimeMillis() - startTime) + " ms");
         }
@@ -298,6 +308,43 @@ public class ExamControlServiceImpl implements ExamControlService {
 
     }
 
+    @Override
+    public void startAnswer(Long examRecordDataId) {
+        Date now = new Date();
+
+        ExamRecordDataEntity examRecordDataEntity =
+                GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        if (null == examRecordDataEntity) {
+            throw new StatusException("100001", "找不到相关考试记录");
+        }
+
+        //如果是断点续考,则需要更新断点续考表中开始答题时间
+        if (null != examRecordDataEntity.getIsContinued() && examRecordDataEntity.getIsContinued()) {
+            //获取最新的一条断点记录
+            ExamContinuedRecordEntity latestExamContinuedRecord =
+                    examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examRecordDataId);
+            if (null == latestExamContinuedRecord) {
+                throw new StatusException("100002", "找不到断点续考记录");
+            }
+
+            latestExamContinuedRecord.setStartTime(now);
+            examContinuedRecordRepo.save(latestExamContinuedRecord);
+        }
+        //第一次开考,更新考试记录中的开始答题时间
+        else {
+            //更新考试记录临时表
+            examRecordDataEntity.setStartTime(now);
+            examRecordDataEntity.setLastActiveTime(now);
+            examRecordDataRepo.save(examRecordDataEntity);
+
+            //更新考试记录缓存
+            ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+            examRecordData.setLastActiveTime(now);
+            examRecordData.setStartTime(now);
+            examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
+        }
+    }
+
     /**
      * 开考预处理
      *
@@ -305,11 +352,6 @@ public class ExamControlServiceImpl implements ExamControlService {
      * @param user
      */
     private void prepare4Exam(Long examStudentId, User user) {
-
-//        // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
-//        String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
-//        SequenceLockHelper.getLock(sequenceLockKey);
-
         SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
         Boolean stuClientLoginLimitBoolean = false;
         if (stuClientLoginLimit.getHasValue()) {
@@ -338,7 +380,7 @@ public class ExamControlServiceImpl implements ExamControlService {
         }
 
         ExamSettingsCacheBean examSettingsCacheBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(),
-                examStudent.getStudentId());
+                examStudent.getStudentId(),examStudent.getExamStageId());
 
         StudentCacheBean studentCacheBean = CacheHelper.getStudent(examStudent.getStudentId());
 
@@ -363,7 +405,7 @@ public class ExamControlServiceImpl implements ExamControlService {
                 }
             }
 
-            // 机构特殊设置开启未配置,不允许考试
+            // 机构特殊设置开启未配置,不允许考试
             if (examSettingsCacheBean.getSpecialSettingsType() == ExamSpecialSettingsType.ORG_BASED) {
                 // 需求调整,所有的组织机构取学生表所关联的orgId
                 Long orgId = CacheHelper.getStudent(examStudent.getStudentId()).getOrgId();
@@ -374,6 +416,15 @@ public class ExamControlServiceImpl implements ExamControlService {
                 }
             }
 
+            //场次特殊设置开启但未配置,不允许考试
+            if (examSettingsCacheBean.getSpecialSettingsType() == ExamSpecialSettingsType.STAGE_BASED) {
+                if (null != examStudent.getExamStageId()) {
+                    ExamStageCacheBean examStage = CacheHelper.getExamStage(examStudent.getExamId(), examStudent.getExamStageId());
+                    if (!examStage.getHasValue()) {
+                        throw new StatusException("100016", "场次配置未完成,不允许考试");
+                    }
+                }
+            }
         }
 
         if (!examSettingsCacheBean.getEnable()
@@ -947,36 +998,58 @@ public class ExamControlServiceImpl implements ExamControlService {
      *
      * @param examId
      * @param studentId
+     * @param examStageId
      * @return
      */
-    private ExamSettingsCacheBean checkExam(Long examId, Long studentId) {
+    private ExamSettingsCacheBean checkExam(Long examId, Long studentId, Long examStageId) {
 
         // 学习中心特殊考试配置(是否禁考,开考时间可以特殊设置)
-        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId,examStageId);
+
         // 如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
         ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(examId,
                 ExamProperties.LIMITED_IF_NO_SPECIAL_SETTINGS.toString());
-        if (examBean.getSpecialSettingsEnabled() && (limitedIfNoSpecialSettings.getHasValue()
-                && Boolean.valueOf(limitedIfNoSpecialSettings.getValue()))) {
+        if (examBean.getSpecialSettingsEnabled()) {
+            if ((limitedIfNoSpecialSettings.getHasValue()
+                    && Boolean.valueOf(limitedIfNoSpecialSettings.getValue()))) {
+                // 学生特殊设置开启未配置,不允许考试
+                if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STUDENT_BASED) {
+                    ExamStudentSettingsCacheBean specialSettings = CacheHelper.getExamStudentSettings(examId, studentId);
+                    if (!specialSettings.getHasValue()) {
+                        throw new StatusException("2001", "考试配置未完成,不允许考试");
+                    }
+                }
 
-            // 学生特殊设置开启未配置,不允许考试
-            if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STUDENT_BASED) {
-                ExamStudentSettingsCacheBean specialSettings = CacheHelper.getExamStudentSettings(examId, studentId);
-                if (!specialSettings.getHasValue()) {
-                    throw new StatusException("2001", "考试配置未完成,不允许考试");
+                // 机构特殊设置开启未配置,不允许考试
+                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("2002", "考试配置未完成,不允许考试");
+                    }
                 }
-            }
 
-            // 机构特殊设置开启未配置,不允许考试
-            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("2002", "考试配置未完成,不允许考试");
+                //场次特殊设置开启但未配置,不允许考试
+                if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STAGE_BASED) {
+                    if (null != examStageId) {
+                        ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
+                        if (!examStage.getHasValue()) {
+                            throw new StatusException("2006", "场次配置未完成,不允许考试");
+                        }
+                    }
                 }
             }
 
+            //场次禁用,不允许考试 TODO 20200814 跟张莹确认一下,场次被禁用了是不能考试还是使用考试的设置???
+            if (examBean.getSpecialSettingsType() == ExamSpecialSettingsType.STAGE_BASED) {
+                if (null != examStageId) {
+                    ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
+                    if (examStage.getHasValue() && !examStage.getEnable()) {
+                        throw new StatusException("2007", "场次被禁用,不允许考试");
+                    }
+                }
+            }
         }
 
         if (!examBean.getEnable() || (examBean.getExamLimit() != null && examBean.getExamLimit())) {
@@ -1228,6 +1301,7 @@ public class ExamControlServiceImpl implements ExamControlService {
         if (examingRecord == null) {
             return null;
         } else {
+            Date now = new Date();
             Integer maxInterruptNum = getMaxInterruptNum(examSessionInfo.getExamId(), examSessionInfo.getRootOrgId(),
                     examSessionInfo.getOrgId());
             CheckExamInProgressInfo checkExamInProgressInfo = new CheckExamInProgressInfo();
@@ -1240,11 +1314,17 @@ public class ExamControlServiceImpl implements ExamControlService {
                 examingRecord.setContinuedCount(continutedCount + 1);
                 examingRecord.setIsContinued(true);
                 examingRecord.setIsExceed(false);
+                examingRecord.setLastActiveTime(now);
+                examingRecord.setContinuedTime(now);
                 checkExamInProgressInfo.setIsExceed(false);
+
+                //添加断点记录
+                addExamContinuedRecord(examingRecord.getId(), now);
             } else {
                 examingRecord.setIsExceed(true);
                 checkExamInProgressInfo.setIsExceed(true);
             }
+            examingRecord.setLastActiveTime(now);
             // 更新考试中的断点续考属性
             examRecordDataService.saveExamRecordDataCache(examingRecord.getId(), examingRecord);
 
@@ -1280,6 +1360,20 @@ public class ExamControlServiceImpl implements ExamControlService {
         }
     }
 
+    /**
+     * 添加断点续考记录
+     *
+     * @param examRecordDataId
+     * @param continuedTime
+     */
+    private void addExamContinuedRecord(Long examRecordDataId, Date continuedTime) {
+        ExamContinuedRecordEntity entity = new ExamContinuedRecordEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setContinuedTime(continuedTime);
+
+        examContinuedRecordRepo.save(entity);
+    }
+
     private ExamRecordData checkExamSession(ExamingSession examSessionInfo, Long studentId) {
         ExamRecordData examingRecord = examRecordDataService
                 .getExamRecordDataCache(examSessionInfo.getExamRecordDataId());

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

@@ -91,7 +91,7 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
         examRecordData.setPaperType(examingSession.getPaperType());
 
-        examRecordData.setStartTime(new Date());
+        examRecordData.setEnterExamTime(new Date());
         examRecordData.setIsContinued(false);
         examRecordData.setContinuedCount(0);
         examRecordData.setFaceSuccessCount(0);