소스 검색

考试时间重新设计

lideyin 4 년 전
부모
커밋
c736caf807

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

@@ -87,14 +87,15 @@ public class ExamControlController extends ControllerSupport {
      */
     @ApiOperation(value = "开始答题")
     @PostMapping("/startAnswer")
-    public void startAnswer(@RequestParam @ApiParam(value = "考试记录id") Long examRecordDataId) {
+    public StartAnswerInfo startAnswer(@RequestParam @ApiParam(value = "考试记录id") Long examRecordDataId) {
         User user = getAccessUser();
         String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
         // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
         SequenceLockHelper.getLock(sequenceLockKey);
         Check.isNull(examRecordDataId, "examRecordDataId不能为空");
 
-        examControlService.startAnswer(examRecordDataId);
+        return examControlService.startAnswer(examRecordDataId);
+
     }
 
     /**

+ 37 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/StartAnswerInfo.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @Description 开始答题
+ * @Author lideyin
+ * @Date 2020/8/18 10:48
+ * @Version 1.0
+ */
+public class StartAnswerInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = 2633999021945517003L;
+
+    private Long examRecordDataId;
+
+    /**
+     * 考试时长
+     */
+    private Long duration;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Long getDuration() {
+        return duration;
+    }
+
+    public void setDuration(Long duration) {
+        this.duration = duration;
+    }
+}

+ 73 - 73
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/StartExamInfo.java

@@ -3,79 +3,79 @@ package cn.com.qmth.examcloud.core.oe.student.bean;
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
 
 /**
- * 
- * @author  	chenken
- * @date    	2018年9月27日 下午4:44:15
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年9月27日 下午4:44:15
+ * @company QMTH
  * @description StartExamInfo.java
  */
-public class StartExamInfo implements JsonSerializable{
-
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 7964522965649314640L;
-	
-	private Long examRecordDataId;
-	
-	private String courseCode;
-	
-	private String courseName;
-	
-	private String studentCode;
-	
-	private String studentName;
-	
-	/**
-	 * 考试时长
-	 */
-	private Integer duration;
-	/**
-	 * 活体检测开始分钟数
-	 */
-	private Integer faceVerifyMinute;
-	
-	public Long getExamRecordDataId() {
-		return examRecordDataId;
-	}
-	public void setExamRecordDataId(Long examRecordDataId) {
-		this.examRecordDataId = examRecordDataId;
-	}
-	public Integer getDuration() {
-		return duration;
-	}
-	public void setDuration(Integer duration) {
-		this.duration = duration;
-	}
-	public Integer getFaceVerifyMinute() {
-		return faceVerifyMinute;
-	}
-	public void setFaceVerifyMinute(Integer faceVerifyMinute) {
-		this.faceVerifyMinute = faceVerifyMinute;
-	}
-	public String getCourseCode() {
-		return courseCode;
-	}
-	public void setCourseCode(String courseCode) {
-		this.courseCode = courseCode;
-	}
-	public String getCourseName() {
-		return courseName;
-	}
-	public void setCourseName(String courseName) {
-		this.courseName = courseName;
-	}
-	public String getStudentCode() {
-		return studentCode;
-	}
-	public void setStudentCode(String studentCode) {
-		this.studentCode = studentCode;
-	}
-	public String getStudentName() {
-		return studentName;
-	}
-	public void setStudentName(String studentName) {
-		this.studentName = studentName;
-	}
-	
+public class StartExamInfo implements JsonSerializable {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 7964522965649314640L;
+
+    private Long examRecordDataId;
+
+    private String courseCode;
+
+    private String courseName;
+
+    private String studentCode;
+
+    private String studentName;
+
+    /**
+     * 活体检测开始分钟数
+     */
+    private Integer faceVerifyMinute;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Integer getFaceVerifyMinute() {
+        return faceVerifyMinute;
+    }
+
+    public void setFaceVerifyMinute(Integer faceVerifyMinute) {
+        this.faceVerifyMinute = faceVerifyMinute;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getStudentCode() {
+        return studentCode;
+    }
+
+    public void setStudentCode(String studentCode) {
+        this.studentCode = studentCode;
+    }
+
+    public String getStudentName() {
+        return studentName;
+    }
+
+    public void setStudentName(String studentName) {
+        this.studentName = studentName;
+    }
+
 }

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

@@ -26,7 +26,7 @@ public interface ExamControlService {
      *
      * @param examRecordDataId
      */
-    void startAnswer(Long examRecordDataId);
+    StartAnswerInfo startAnswer(Long examRecordDataId);
 
     /**
      * 交卷

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

@@ -3,6 +3,7 @@ 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.enums.ExamStageStartExamStatus;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.enums.SubmitType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
@@ -309,17 +310,57 @@ public class ExamControlServiceImpl implements ExamControlService {
     }
 
     @Override
-    public void startAnswer(Long examRecordDataId) {
+    public StartAnswerInfo startAnswer(Long examRecordDataId) {
         Date now = new Date();
 
-        ExamRecordDataEntity examRecordDataEntity =
-                GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        if (null == examRecordDataEntity) {
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (null == examRecordData) {
             throw new StatusException("100001", "找不到相关考试记录");
         }
 
+        ExamingSession examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
+        if (null == examingSession) {
+            throw new StatusException("100002", "无效的会话,请离开考试");
+        }
+
+        StartAnswerInfo resultInfo = new StartAnswerInfo();
+        resultInfo.setExamRecordDataId(examRecordDataId);
+
+        //第一次开考,更新考试记录中的开始答题时间
+        if (null == examRecordData.getIsContinued() || !examRecordData.getIsContinued()) {
+            //如果已经调用过开始作答接口,则不做任何处理
+            if (null != examRecordData.getStartTime()) {
+                examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
+                resultInfo.setDuration(examingSession.getExamDuration());//直接返回考试会话中的考试时长
+                return resultInfo;
+            }
+
+            //更新考试记录缓存
+            examRecordData.setLastActiveTime(now);
+            examRecordData.setStartTime(now);
+            examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
+
+            //更新考试会话中的作答时间,实际考试总时长
+            examingSession = examingSessionService.getExamingSession(examRecordData.getStudentId());
+            examingSession.setStartTime(now.getTime());
+            long actualExamTotalMilliSeconds = calcExamTotalMilliSeconds(examRecordDataId);
+            examingSession.setExamDuration(actualExamTotalMilliSeconds);
+            examingSessionService.saveExamingSession(examRecordData.getStudentId(), examingSession);
+
+            resultInfo.setDuration(actualExamTotalMilliSeconds);
+            return resultInfo;
+        }
+
         //如果是断点续考,则需要更新断点续考表中开始答题时间
-        if (null != examRecordDataEntity.getIsContinued() && examRecordDataEntity.getIsContinued()) {
+        else {
+            //如果断点续考之前未成功调用过开始答题接口,则需要首先更新考试记录中的首次答题时间
+            if (null == examRecordData.getStartTime()) {
+                //更新考试记录缓存
+                examRecordData.setLastActiveTime(now);
+                examRecordData.setStartTime(now);
+                examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
+            }
+
             //获取最新的一条断点记录
             ExamContinuedRecordEntity latestExamContinuedRecord =
                     examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examRecordDataId);
@@ -328,31 +369,80 @@ public class ExamControlServiceImpl implements ExamControlService {
             }
 
             //如果断点续考后,已经调用过答题接口,则不再处理
-            if (null !=latestExamContinuedRecord.getStartTime()) {
-                return;
+            if (null != latestExamContinuedRecord.getStartTime()) {
+                resultInfo.setDuration(examingSession.getExamDuration());
+                return resultInfo;
             }
 
+            //更新断点续考中的答题时间
             latestExamContinuedRecord.setStartTime(now);
             examContinuedRecordRepo.save(latestExamContinuedRecord);
+
+            resultInfo.setDuration(examingSession.getExamDuration());
+            return resultInfo;
         }
-        //第一次开考,更新考试记录中的开始答题时间
-        else {
-            //更新考试记录临时表
-//            examRecordDataEntity.setStartTime(now);
-//            examRecordDataEntity.setLastActiveTime(now);
-//            examRecordDataRepo.save(examRecordDataEntity);
 
-            ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
-            //如果已经调用过开始作答接口,则不做任何处理
-            if (null != examRecordData.getStartTime()) {
-                return;
-            }
+    }
 
-            //更新考试记录缓存
-            examRecordData.setLastActiveTime(now);
-            examRecordData.setStartTime(now);
-            examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordData);
+    /**
+     * 计算单次考试总时长(毫秒)
+     *
+     * @param examRecordDataId 考试记录id
+     * @return
+     */
+    private long calcExamTotalMilliSeconds(Long examRecordDataId) {
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (null == examRecordData) {
+            throw new StatusException("100001", "找不到相关考试记录");
+        }
+        Long examId = examRecordData.getExamId();
+        Long studentId = examRecordData.getStudentId();
+        Long examStageId = examRecordData.getExamStageId();
+
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId, examStageId);
+        //如果未启用特殊设置,直接返回考试设置的默认考试时长
+        if (!cachedExam.getSpecialSettingsEnabled()) {
+            return cachedExam.getDuration() * 60 * 1000;
+        }
+
+        switch (cachedExam.getSpecialSettingsType()) {
+            case ORG_BASED:
+                return cachedExam.getDuration() * 60 * 1000;
+            case STAGE_BASED:
+                //原始的(即考试中定义的)考试时长
+                long originalExamTotalSeconds = cachedExam.getDuration() * 60 * 1000;
+
+                if (null != examStageId) {
+                    ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
+                    if (!examStage.getSpecialSetting()) {
+                        return originalExamTotalSeconds;
+                    }
+
+                    if (SubmitType.NORMAL.name() == examStage.getSubmitType()) {
+                        return originalExamTotalSeconds;
+                    }
+
+                    //如果是定点交卷需要特殊处理,否则直接返回考试中定义的考试时长
+                    if (examStage.getSpecialSetting() && SubmitType.TIMING_END.name() == examStage.getSubmitType()) {
+                        //定点交卷时间 = 开始进入考试的起始值 + 定点交卷时长
+                        Date fixedSubmitTime = DateUtils.addMinutes(examStage.getStartTime(), examStage.getSubmitDuration());
+                        //实际开始答题时间
+                        Date startAnswerTime = examRecordData.getStartTime();
+                        //理论上考试时长 = (定点交卷时间 - 实际开始答题时间)
+                        long tempTotalSeconds = fixedSubmitTime.getTime() - startAnswerTime.getTime();
+
+                        return tempTotalSeconds <= originalExamTotalSeconds
+                                ? tempTotalSeconds
+                                : originalExamTotalSeconds;
+                    }
+                }
+
+                return originalExamTotalSeconds;
+            default:
+                throw new StatusException("100002",
+                        String.format("系统暂不支持的特殊设置:%s", cachedExam.getSpecialSettingsType().name()));
         }
+
     }
 
     /**
@@ -1250,7 +1340,7 @@ public class ExamControlServiceImpl implements ExamControlService {
         StartExamInfo startExamInfo = new StartExamInfo();
         startExamInfo.setExamRecordDataId(examRecordDataId);
         startExamInfo.setCourseName(courseBean.getName());
-        startExamInfo.setDuration(examBean.getDuration());
+//        startExamInfo.setDuration(examBean.getDuration());
         startExamInfo.setFaceVerifyMinute(getFaceVerifyMinute(examingSession.getRootOrgId(), examBean.getId(),
                 examingSession.getOrgId(), examingSession.getStudentId()));
         return startExamInfo;
@@ -1499,34 +1589,66 @@ public class ExamControlServiceImpl implements ExamControlService {
         ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
         if (examSessionInfo == null
                 || examSessionInfo.getExamingStatus().equals(ExamingStatus.INFORMAL)) {
-            throw new StatusException("8001", "无效的会话,请离开考试");
+            throw new StatusException("101001", "无效的会话,请离开考试");
+        }
+
+        ExamRecordData examRecordDataCache =
+                examRecordDataService.getExamRecordDataCache(examSessionInfo.getExamRecordDataId());
+        //如果没有开始作答时间,则抛出异常
+        if (null == examRecordDataCache.getStartTime()) {
+            throw new StatusException("101002", "请先执行作答");
         }
 
+        //如果已用考试时间超过考试总时长,则不允许继续调用心跳接口
         String examingHeartbeatKey = RedisKeyHelper.getBuilder().examingHeartbeatKey(examSessionInfo.getExamRecordDataId());
         ExamingHeartbeat examingHeartbeat = redisClient.get(examingHeartbeatKey, ExamingHeartbeat.class);
-
         if (null != examingHeartbeat
                 && (examingHeartbeat.getCost() * 1000) >= examSessionInfo.getExamDuration()) {
-            throw new StatusException("8002", "考试会话已过期,请重新开考");
+            throw new StatusException("101003", "考试会话已过期,请重新开考");
         }
 
         if (null == examingHeartbeat) {
             examingHeartbeat = new ExamingHeartbeat();
-            examingHeartbeat.setCost(-60L);
-            examingHeartbeat.setTimes(0L);
             examingHeartbeat.setExamRecordDataId(examSessionInfo.getExamRecordDataId());
+            examingHeartbeat.setCost(0L);
         }
 
-        examingHeartbeat.setTimes(examingHeartbeat.getTimes() + 1);
-        examingHeartbeat.setCost(examingHeartbeat.getCost() + 60);
+        boolean isContinued =
+                (examRecordDataCache.getIsContinued() != null && examRecordDataCache.getIsContinued()) ? true : false;
+
+        Date now = new Date();
+        long usedExamSeconds = examingHeartbeat.getCost();
 
-        redisClient.set(examingHeartbeatKey, examingHeartbeat);
+        //无断点续考,考试已用时间 = 当前时间 - 作答时间
+        if (!isContinued) {
+            usedExamSeconds = (now.getTime() - examRecordDataCache.getStartTime().getTime()) / 1000;
+        }
+
+        //如果有断点续考,考试已用时间 = 考试已用时间 + (当前时间 - 最近一次的作答时间)
+        else {
+            //获取最新的一条断点记录
+            ExamContinuedRecordEntity latestExamContinuedRecord =
+                    examContinuedRecordRepo.findTopByExamRecordDataIdOrderByIdDesc(examSessionInfo.getExamRecordDataId());
+            if (null == latestExamContinuedRecord) {
+                throw new StatusException("101004", "找不到断点续考记录");
+            }
+
+            if (null == latestExamContinuedRecord.getStartTime()) {
+                throw new StatusException("101005", "请先执行作答");
+            }
+
+            usedExamSeconds = usedExamSeconds + (now.getTime() - latestExamContinuedRecord.getStartTime().getTime()) / 1000;
+        }
+
+        examingHeartbeat.setCost(usedExamSeconds);
+        redisClient.set(examingHeartbeatKey, examingHeartbeat);//更新心跳缓存
 
         setAndSaveActiveTime(examSessionInfo.getExamRecordDataId());
 
         // 在线考生心跳打点
         ReportsUtil.report(new OnlineExamStudentReport(user.getRootOrgId(), user.getUserId(),
                 examSessionInfo.getExamId(), examSessionInfo.getExamStudentId()));
+
         // 返回考试剩余时间
         return examSessionInfo.getExamDuration() - (examingHeartbeat.getCost() * 1000);
     }