Эх сурвалжийг харах

Merge branch 'dev'

# Conflicts:
#	themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java
wangliang 4 жил өмнө
parent
commit
c5906647d8

+ 2 - 2
themis-backend/src/main/java/com/qmth/themis/backend/api/TEExamReexamController.java

@@ -167,7 +167,7 @@ public class TEExamReexamController {
                 TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
                 List<TEExamReexam> teExamReexamUpdateList = new ArrayList<>();
                 for (String reexamId : reexamIdList) {
-                    if (redisUtil.lock(SystemConstant.REDIS_LOCK_REEXAM_AUDITING + Long.parseLong(reexamId), SystemConstant.REDIS_LOCK_REEXAM_TIME_OUT)) {
+                    if (redisUtil.lock(SystemConstant.REDIS_LOCK_REEXAM_AUDITING_PREFIX + Long.parseLong(reexamId), SystemConstant.REDIS_LOCK_REEXAM_TIME_OUT)) {
                         TEExamReexam teExamReexam = teExamReexamService.getById(Long.parseLong(reexamId));
                         if (Objects.isNull(teExamReexam)) {
                             throw new BusinessException("重考id[" + reexamId + "]记录不存在");
@@ -215,7 +215,7 @@ public class TEExamReexamController {
         } finally {
             if (Objects.nonNull(reexamIdList)) {
                 reexamIdList.forEach(s -> {
-                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_REEXAM_AUDITING + s);
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_REEXAM_AUDITING_PREFIX + s);
                 });
             }
         }

+ 6 - 1
themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java

@@ -103,12 +103,17 @@ public class SystemConstant {
      */
     public static final String REDIS_LOCK_MQ_PREFIX = "lock:mq:";
     public static final String REDIS_LOCK_WEBSOCKET_PREFIX = "lock:websocket:";
-    public static final String REDIS_LOCK_REEXAM_AUDITING = "lock:reexam:";
+    public static final String REDIS_LOCK_REEXAM_AUDITING_PREFIX = "lock:reexam:";
+    public static final String REDIS_LOCK_EXAM_BREAK_LOGIC_PREFIX = "lock:exam:break:logic:";
+    public static final String REDIS_LOCK_EXAM_BREAK_PREFIX = "lock:exam:break:";
     public static final long REDIS_LOCK_MQ_TIME_OUT = 60L;
     public static final long REDIS_LOCK_WEBSOCKET_TIME_OUT = 30L;
     public static final long REDIS_LOCK_REEXAM_TIME_OUT = 30L;
     public static final long REDIS_LOCK_REEXAM_EXAM_STUDENT_TIME_OUT = 30L;
     public static final long REDIS_CACHE_TIME_OUT = 30L;
+    public static final long REDIS_LOCK_EXAM_BREAK_LOGIC_TIME_OUT = 60L;
+    public static final long REDIS_LOCK_EXAM_BREAK_TIME_OUT = 60L;
+
     //学生锁
     public static final String REDIS_LOCK_STUDENT_PREFIX = "lock:student:student_id_";
     //考生锁

+ 111 - 118
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -1,49 +1,14 @@
 package com.qmth.themis.business.service.impl;
 
-import java.io.File;
-import java.math.BigDecimal;
-import java.text.SimpleDateFormat;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-
-import javax.annotation.Resource;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
 import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.qmth.themis.business.bean.backend.InvigilateListBean;
-import com.qmth.themis.business.bean.backend.InvigilateListHistoryBean;
-import com.qmth.themis.business.bean.backend.InvigilateListPatrolBean;
-import com.qmth.themis.business.bean.backend.InvigilateListProgressBean;
-import com.qmth.themis.business.bean.backend.InvigilateListProgressExcelBean;
-import com.qmth.themis.business.bean.backend.InvigilateListVideoBean;
-import com.qmth.themis.business.bean.backend.InvigilateListWarningBean;
-import com.qmth.themis.business.bean.backend.OpenRecordNeedMarkBean;
+import com.qmth.themis.business.bean.backend.*;
 import com.qmth.themis.business.cache.ExamActivityRecordCacheUtil;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.cache.RedisKeyHelper;
-import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
-import com.qmth.themis.business.cache.bean.ExamActivityRecordCacheBean;
-import com.qmth.themis.business.cache.bean.ExamCacheBean;
-import com.qmth.themis.business.cache.bean.ExamPaperCacheBean;
-import com.qmth.themis.business.cache.bean.ExamStudentAnswerCacheBean;
-import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
-import com.qmth.themis.business.cache.bean.ExamStudentPaperStructCacheBean;
-import com.qmth.themis.business.cache.bean.ObjectiveAnswerCacheBean;
+import com.qmth.themis.business.cache.bean.*;
 import com.qmth.themis.business.config.SystemConfig;
 import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
@@ -53,29 +18,28 @@ import com.qmth.themis.business.dto.response.MarkResultDto;
 import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
 import com.qmth.themis.business.entity.TOeExamAnswer;
 import com.qmth.themis.business.entity.TOeExamRecord;
-import com.qmth.themis.business.enums.ExamRecordFieldEnum;
-import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.ExamTypeEnum;
-import com.qmth.themis.business.enums.ExceptionEnum;
-import com.qmth.themis.business.enums.FinishTypeEnum;
-import com.qmth.themis.business.enums.LivenessTypeEnum;
-import com.qmth.themis.business.enums.MqTagEnum;
-import com.qmth.themis.business.enums.MqTopicEnum;
-import com.qmth.themis.business.enums.ObjectiveScorePolicyEnum;
-import com.qmth.themis.business.enums.RecordSelectStrategyEnum;
-import com.qmth.themis.business.enums.VerifyExceptionEnum;
-import com.qmth.themis.business.service.MqDtoService;
-import com.qmth.themis.business.service.TEExamActivityService;
-import com.qmth.themis.business.service.TEExamPaperService;
-import com.qmth.themis.business.service.TEExamService;
-import com.qmth.themis.business.service.TEExamStudentService;
-import com.qmth.themis.business.service.TOeExamAnswerService;
-import com.qmth.themis.business.service.TOeExamRecordService;
+import com.qmth.themis.business.enums.*;
+import com.qmth.themis.business.service.*;
 import com.qmth.themis.business.util.OssUtil;
 import com.qmth.themis.business.util.RedisUtil;
 import com.qmth.themis.common.contanst.Constants;
+import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.common.util.FileUtil;
 import com.qmth.themis.common.util.SimpleBeanUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.*;
 
 /**
  * @Description: 考试记录 服务实现类
@@ -216,7 +180,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
         ExamStudentAnswerCacheBean answer = (ExamStudentAnswerCacheBean) redisUtil.get(
                 RedisKeyHelper.examAnswerKey(recordId), key);
         if (answer == null) {
-            log.error("no ExamStudentAnswerCacheBean for calculateObjectiveScore recordId:"+recordId+" key:" + key);
+            log.error("no ExamStudentAnswerCacheBean for calculateObjectiveScore recordId:" + recordId + " key:" + key);
 
             // 计算客观分总分
             calculateTotalObjectiveScore(param);
@@ -225,7 +189,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
         //整卷客观题标答缓存集合
         Map<String, ObjectiveAnswerCacheBean> map = examPaperService.getObjectiveAnswerCacheBean(paperId);
         if (map == null || map.size() == 0) {
-            log.info("no ObjectiveAnswerCacheBean map for calculateObjectiveScore recordId:"+recordId+" paperId:" + paperId);
+            log.info("no ObjectiveAnswerCacheBean map for calculateObjectiveScore recordId:" + recordId + " paperId:" + paperId);
             // 更新分数
             answer.setScore(0.0);
             redisUtil.set(RedisKeyHelper.examAnswerKey(recordId), key, answer);
@@ -237,7 +201,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
         //客观题标答缓存
         ObjectiveAnswerCacheBean cb = map.get(key);
         if (cb == null) {
-            log.info("no ObjectiveAnswerCacheBean for calculateObjectiveScore recordId:"+recordId+" key:" + key);
+            log.info("no ObjectiveAnswerCacheBean for calculateObjectiveScore recordId:" + recordId + " key:" + key);
             // 更新分数
             answer.setScore(0.0);
             redisUtil.set(RedisKeyHelper.examAnswerKey(recordId), key, answer);
@@ -302,16 +266,16 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
 
     @SuppressWarnings("unchecked")
     private void calculateTotalObjectiveScore(Map<String, Object> param) {
-    	Long recordId = (Long) param.get("recordId");
+        Long recordId = (Long) param.get("recordId");
         Integer mainNumber = (Integer) param.get("mainNumber");
         Integer subNumber = (Integer) param.get("subNumber");
         Integer subIndex = (Integer) param.get("subIndex");
         String lockKey = SystemConstant.REDIS_LOCK_TOTAL_OBJECTIVE_SCORE_PREFIX + recordId;
         try {
-        	String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
+            String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
             Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
             if (lock) {
-            	log.info("calculateTotalObjectiveScore get lock sucss recordId:"+recordId+" key:"+key);
+                log.info("calculateTotalObjectiveScore get lock sucss recordId:"+recordId+" key:"+key);
                 Map<String, ExamStudentAnswerCacheBean> as = redisUtil.getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
                 if (as != null && as.size() > 0) {
                     Double total = 0.0;
@@ -320,7 +284,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                             if (sa.getScore() != null) {//有分值
                                 total = total + sa.getScore();
                             } else {
-                            	log.info("calculateTotalObjectiveScore ExamStudentAnswerCacheBean Score is null recordId:"+recordId+" key:"+RedisKeyHelper.examAnswerHashKey(sa.getMainNumber(), sa.getSubNumber(), sa.getSubIndex()));
+                                log.info("calculateTotalObjectiveScore ExamStudentAnswerCacheBean Score is null recordId:"+recordId+" key:"+RedisKeyHelper.examAnswerHashKey(sa.getMainNumber(), sa.getSubNumber(), sa.getSubIndex()));
                                 total = null;
                                 break;
                             }
@@ -329,18 +293,18 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                     if (total != null) {
                         ExamRecordCacheUtil.setObjectiveScore(recordId, total, true);
                     }else {
-                    	log.info("calculateTotalObjectiveScore total Score is null recordId:"+recordId+" key:"+key);
+                        log.info("calculateTotalObjectiveScore total Score is null recordId:"+recordId+" key:"+key);
                     }
                 }
             }else {
-            	log.info("calculateTotalObjectiveScore get lock faild recordId:"+recordId+" key:"+key);
-            	calculateObjectiveScoreMsg(recordId, mainNumber, subNumber, subIndex);
+                log.info("calculateTotalObjectiveScore get lock faild recordId:"+recordId+" key:"+key);
+                calculateObjectiveScoreMsg(recordId, mainNumber, subNumber, subIndex);
             }
         } finally {
             redisUtil.releaseLock(lockKey);
         }
     }
-    
+
     //考试中计算客观分消息
     private void calculateObjectiveScoreMsg(Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex) {
         Map<String, Object> transMap = new HashMap<String, Object>();
@@ -348,18 +312,18 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
         transMap.put("mainNumber", mainNumber);
         transMap.put("subNumber", subNumber);
         transMap.put("subIndex", subIndex);
-        
-		String level = "1s";
-		Integer time = SystemConstant.mqDelayLevel.get(level);
-		LocalDateTime dt = LocalDateTime.now();
-		dt = dt.plusSeconds(Long.parseLong(level.replace("s", "")));
-		Map<String, Object> propMap = new HashMap<String, Object>();
-		propMap.put("timeOut", time);
-		propMap.put("mqExecTime", dt.toInstant(ZoneOffset.of("+8")).toEpochMilli());
-		MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.CALCULATE_OBJECTIVE_SCORE.name(),
-				transMap, MqTagEnum.CALCULATE_OBJECTIVE_SCORE, recordId.toString(), propMap, recordId.toString());
-
-		mqDtoService.assembleSendAsyncDelayMsg(mqDto);
+
+        String level = "1s";
+        Integer time = SystemConstant.mqDelayLevel.get(level);
+        LocalDateTime dt = LocalDateTime.now();
+        dt = dt.plusSeconds(Long.parseLong(level.replace("s", "")));
+        Map<String, Object> propMap = new HashMap<String, Object>();
+        propMap.put("timeOut", time);
+        propMap.put("mqExecTime", dt.toInstant(ZoneOffset.of("+8")).toEpochMilli());
+        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.CALCULATE_OBJECTIVE_SCORE.name(),
+                transMap, MqTagEnum.CALCULATE_OBJECTIVE_SCORE, recordId.toString(), propMap, recordId.toString());
+
+        mqDtoService.assembleSendAsyncDelayMsg(mqDto);
     }
 
     private boolean checkSingleChoice(String answerArray, JSONArray ar) {
@@ -992,26 +956,41 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Override
     @Transactional
     public void setExamBreak(Integer alreadyBreakCount, Long recordId, Long examActivityId) {
-        alreadyBreakCount++;
-        Long breakId = Constants.idGen.next();
-        ExamRecordCacheUtil.setLastBreakId(recordId, breakId, false);
-        ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF, false);
-        Long lastBreakTimeNow = System.currentTimeMillis();
-        ExamRecordCacheUtil.setLastBreakTime(recordId, lastBreakTimeNow, false);
-        ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount, false);
-        Long lastStartTime = System.currentTimeMillis();
-        ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
-        String[] columns = new String[]{ExamRecordFieldEnum.last_break_id.name(), ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_break_time.name(), ExamRecordFieldEnum.already_break_count.name(), ExamRecordFieldEnum.last_start_time.name()};
-        Object[] values = new Object[]{breakId, ExamRecordStatusEnum.BREAK_OFF, lastBreakTimeNow, alreadyBreakCount, lastStartTime};
-        TOeExamRecordService tOeExamRecordService = SpringContextHolder.getBean(TOeExamRecordService.class);
-        tOeExamRecordService.dataUpdatesMq(recordId, columns, values);
-        //考试断点异常原因 发送mq start
-        MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
-        MqDtoService mqDtoService = SpringContextHolder.getBean(MqDtoService.class);
-        mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
-        //考试断点异常原因 发送mq end
-        //更新场次-考试记录缓存
-        ExamActivityRecordCacheUtil.setExamRecordStatus(examActivityId, recordId, new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId), ExamRecordCacheUtil.getStatus(recordId)));
+        try {
+            if (redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_BREAK_PREFIX + recordId, SystemConstant.REDIS_LOCK_EXAM_BREAK_TIME_OUT)) {
+                alreadyBreakCount++;
+                Long breakId = Constants.idGen.next();
+                ExamRecordCacheUtil.setLastBreakId(recordId, breakId, false);
+                ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF, false);
+                Long lastBreakTimeNow = System.currentTimeMillis();
+                ExamRecordCacheUtil.setLastBreakTime(recordId, lastBreakTimeNow, false);
+                ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount, false);
+                Long lastStartTime = System.currentTimeMillis();
+                ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
+                String[] columns = new String[]{ExamRecordFieldEnum.last_break_id.name(), ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_break_time.name(), ExamRecordFieldEnum.already_break_count.name(), ExamRecordFieldEnum.last_start_time.name()};
+                Object[] values = new Object[]{breakId, ExamRecordStatusEnum.BREAK_OFF, lastBreakTimeNow, alreadyBreakCount, lastStartTime};
+                TOeExamRecordService tOeExamRecordService = SpringContextHolder.getBean(TOeExamRecordService.class);
+                tOeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                //考试断点异常原因 发送mq start
+                MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
+                MqDtoService mqDtoService = SpringContextHolder.getBean(MqDtoService.class);
+                mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
+                //考试断点异常原因 发送mq end
+                //更新场次-考试记录缓存
+                ExamActivityRecordCacheUtil.setExamRecordStatus(examActivityId, recordId, new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId), ExamRecordCacheUtil.getStatus(recordId)));
+            }
+        } catch (Exception e) {
+            log.error("请求出错", e);
+            if (e instanceof BusinessException) {
+                throw new BusinessException(e.getMessage());
+            } else {
+                throw new RuntimeException(e);
+            }
+        } finally {
+            if (Objects.nonNull(recordId)) {
+                redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_BREAK_PREFIX + recordId);
+            }
+        }
     }
 
     /**
@@ -1024,28 +1003,42 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Override
     public Boolean sendExamBreakMsg(Long recordId, boolean setBreak) {
         Boolean finished = false;
-        Long examId = ExamRecordCacheUtil.getExamId(recordId);
-        Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
-        ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
-        ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
-
-        Long lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
-        Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ? 0 : ec.getBreakExpireSeconds();
-        Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
-        Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
-        Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
-        if (Objects.nonNull(lastBreakTime) && (System.currentTimeMillis() - lastBreakTime) / 1000 >= breakExpireSeconds) {
-            finished = true;
-            examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
-        } else {
-            if (leftBreakResumeCount < 0) {
-                finished = true;
-                examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
-            } else {
-                if (setBreak) {
-                    this.setExamBreak(alreadyBreakCount, recordId, examStudentCacheBean.getExamActivityId());
+        try {
+            if (redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_BREAK_LOGIC_PREFIX + recordId, SystemConstant.REDIS_LOCK_EXAM_BREAK_LOGIC_TIME_OUT)) {
+                Long examId = ExamRecordCacheUtil.getExamId(recordId);
+                Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
+                ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
+                ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
+                Long lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
+                Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ? 0 : ec.getBreakExpireSeconds();
+                Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
+                Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
+                Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
+                if (Objects.nonNull(lastBreakTime) && (System.currentTimeMillis() - lastBreakTime) / 1000 >= breakExpireSeconds) {
+                    finished = true;
+                    examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
+                } else {
+                    if (leftBreakResumeCount < 0) {
+                        finished = true;
+                        examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
+                    } else {
+                        if (setBreak) {
+                            this.setExamBreak(alreadyBreakCount, recordId, examStudentCacheBean.getExamActivityId());
+                        }
+                    }
                 }
             }
+        } catch (Exception e) {
+            log.error("请求出错", e);
+            if (e instanceof BusinessException) {
+                throw new BusinessException(e.getMessage());
+            } else {
+                throw new RuntimeException(e);
+            }
+        } finally {
+            if (Objects.nonNull(recordId)) {
+                redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_BREAK_LOGIC_PREFIX + recordId);
+            }
         }
         return finished;
     }

+ 7 - 6
themis-exam/src/main/java/com/qmth/themis/exam/api/TEStudentController.java

@@ -244,18 +244,19 @@ public class TEStudentController {
             ExamCacheBean ec = teExamService.getExamCacheBean(ecExamId);//考试缓存
             ExamActivityCacheBean examActivityCacheBean = teExamActivityService.getExamActivityCacheBean(examActivityId);//考试场次缓存
             ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
-            Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
             //如果断点时间大于整体断点时间,则强制交卷
             if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING) || Objects.equals(status, ExamRecordStatusEnum.BREAK_OFF) || Objects.equals(status, ExamRecordStatusEnum.RESUME_PREPARE)) {
+                //只有ANSWERING状态才生成断点
+                if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
+                    //先生成断点,再比较
+                    ExamConstant.sendExamStopMsg(recordId, true);
+                    Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
+                    tOeExamRecordService.setExamBreak(alreadyBreakCount, recordId, examActivityId);
+                }
                 Boolean finished = tOeExamRecordService.sendExamBreakMsg(recordId, false);
                 if (finished) {
                     map = this.getWaitList(teStudent.getId(), examId, orgId, map);
                 } else {
-                    //只有ANSWERING状态才生成断点
-                    if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
-                        ExamConstant.sendExamStopMsg(recordId, true);
-                        tOeExamRecordService.setExamBreak(alreadyBreakCount, recordId, examActivityId);
-                    }
                     ExamUnFinishBean examUnFinishBean = this.unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean, examStudentId);
                     map.put("unFinished", examUnFinishBean);
                 }

+ 4 - 1
themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

@@ -243,7 +243,10 @@ public class MqLogicServiceImpl implements MqLogicService {
         ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
         WebsocketStatusEnum websocketStatusEnum = ExamRecordCacheUtil.getClientWebsocketStatus(recordId);
         if (Objects.nonNull(websocketStatusEnum) && !Objects.equals(websocketStatusEnum, WebsocketStatusEnum.ON_LINE) && (!Objects.equals(status, ExamRecordStatusEnum.FIRST_PREPARE) || !Objects.equals(status, ExamRecordStatusEnum.FINISHED) || !Objects.equals(status, ExamRecordStatusEnum.PERSISTED))) {
-            examRecordService.sendExamBreakMsg(recordId, true);
+            //先生成断点,再比较
+            Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
+            examRecordService.setExamBreak(alreadyBreakCount, recordId, ExamRecordCacheUtil.getExamActivityId(recordId));
+            examRecordService.sendExamBreakMsg(recordId, false);
             //更新客户端摄像头推流状态为stop
             MonitorStatusSourceEnum cameraStatusSourceEnum = ExamRecordCacheUtil.getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA);
             if (Objects.nonNull(cameraStatusSourceEnum)) {