Pārlūkot izejas kodu

断点逻辑修改,加入examStatus锁,加入全局异常错误信息表

wangliang 4 gadi atpakaļ
vecāks
revīzija
b22f88b0e6

+ 3 - 7
themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java

@@ -171,9 +171,7 @@ public class SystemConstant {
 
     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 String REDIS_LOCK_EXAM_STATUS_PREFIX = "lock:exam_status:record:";
 
     public static final long REDIS_LOCK_MQ_TIME_OUT = 60L;
 
@@ -185,9 +183,7 @@ public class SystemConstant {
 
     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 long REDIS_LOCK_EXAM_STATUS_TIME_OUT = 60L;
 
     //学生锁
     public static final String REDIS_LOCK_STUDENT_PREFIX = "lock:student:student_id_";
@@ -211,7 +207,7 @@ public class SystemConstant {
     public static final String REDIS_LOCK_CALCULATE_SCORE_PAPER_IMPORT_PREFIX = "lock:calculate_score_paper_import:exam_id_";
 
     //交卷锁
-    public static final String REDIS_LOCK_FINISH_EXXAM_PREFIX = "lock:finish_exxam:record_id_";
+    public static final String REDIS_LOCK_FINISH_EXAM_PREFIX = "lock:finish_exam:record_id_";
 
     /**
      * redis过期时间

+ 18 - 0
themis-business/src/main/java/com/qmth/themis/business/dao/TGErrorMapper.java

@@ -0,0 +1,18 @@
+package com.qmth.themis.business.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.themis.business.entity.TBRole;
+import com.qmth.themis.business.entity.TGError;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @Description: 全局异常错误信息 Mapper 接口
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/11/17
+ */
+@Mapper
+public interface TGErrorMapper extends BaseMapper<TGError> {
+
+}

+ 76 - 0
themis-business/src/main/java/com/qmth/themis/business/entity/TGError.java

@@ -0,0 +1,76 @@
+package com.qmth.themis.business.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.themis.business.util.UidUtil;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 全局异常错误信息表
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/11/17
+ */
+@ApiModel(value = "t_g_error", description = "全局异常错误信息表")
+public class TGError implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "业务信息")
+    @TableField(value = "obj")
+    private String obj;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Long createTime;
+
+    public TGError() {
+
+    }
+
+    public TGError(String obj, Long createTime) {
+        this.id = UidUtil.nextId();
+        this.obj = obj;
+        this.createTime = createTime;
+    }
+
+    public static long getSerialVersionUID() {
+        return serialVersionUID;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getObj() {
+        return obj;
+    }
+
+    public void setObj(String obj) {
+        this.obj = obj;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+}

+ 15 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TGErrorService.java

@@ -0,0 +1,15 @@
+package com.qmth.themis.business.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.themis.business.entity.TGError;
+
+/**
+ * @Description: 全局异常错误信息 服务类
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/11/17
+ */
+public interface TGErrorService extends IService<TGError> {
+
+}

+ 2 - 1
themis-business/src/main/java/com/qmth/themis/business/service/TOeExamRecordService.java

@@ -408,8 +408,9 @@ public interface TOeExamRecordService extends IService<TOeExamRecord> {
      * 设置断点信息
      *
      * @param recordId
+     * @param count
      */
-    void setExamBreak(Long recordId);
+    Boolean setExamBreak(Long recordId, Integer count);
 
     /**
      * 发送断点信息

+ 78 - 41
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -511,6 +511,33 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         ret.setPaperDecryptSecret(ep.getDecryptSecret());
         ret.setPaperDecryptVector(ep.getDecryptVector());
 
+        boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
+                SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
+        if (lock) {
+            try {
+                // 更新考试记录缓存
+                Long firstStartTime = System.currentTimeMillis();
+                ExamRecordCacheUtil.setFirstStartTime(recordId, firstStartTime, false);
+                ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.ANSWERING, false);
+                Long lastStartTime = System.currentTimeMillis();
+                ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
+                String[] columns = new String[]{ExamRecordFieldEnum.first_start_time.name(),
+                        ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_start_time.name()};
+                Object[] values = new Object[]{firstStartTime, ExamRecordStatusEnum.ANSWERING, lastStartTime};
+                toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                //更新场次-考试记录缓存
+                ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId,
+                        new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                ExamRecordCacheUtil.getStatus(recordId)));
+            } finally {
+                if (Objects.nonNull(recordId)) {
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                }
+            }
+        } else {
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
+
         if (!ExamRecordStatusEnum.RESUME_PREPARE.equals(sta)) {//非断点进入的
             // 写入次数
             es.setAlreadyExamCount(es.getAlreadyExamCount() + 1);
@@ -518,20 +545,6 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             redisUtil.set(RedisKeyHelper.examStudentCacheKey(examStudentId), es);
             updateExamStudent(examStudentId, es.getAlreadyExamCount(), recordId);
         }
-        // 更新考试记录缓存
-        Long firstStartTime = System.currentTimeMillis();
-        ExamRecordCacheUtil.setFirstStartTime(recordId, firstStartTime, false);
-        ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.ANSWERING, false);
-        Long lastStartTime = System.currentTimeMillis();
-        ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
-        String[] columns = new String[]{ExamRecordFieldEnum.first_start_time.name(),
-                ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_start_time.name()};
-        Object[] values = new Object[]{firstStartTime, ExamRecordStatusEnum.ANSWERING, lastStartTime};
-        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
-        //更新场次-考试记录缓存
-        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId,
-                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
-                        ExamRecordCacheUtil.getStatus(recordId)));
 
         //非强制交卷,换算最终交卷时间并生成一次性延时任务
         if (Objects.nonNull(exam.getForceFinish()) && exam.getForceFinish().intValue() == 0) {
@@ -854,17 +867,29 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             ret.setAudioLeftPlayCount(audioLeftPlayCounts);
         }
 
-        Long lastPrepareTime = System.currentTimeMillis();
-        ExamRecordCacheUtil.setLastPrepareTime(recordId, lastPrepareTime, false);
-        ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.RESUME_PREPARE, false);
-        String[] columns = new String[]{ExamRecordFieldEnum.last_prepare_time.name(),
-                ExamRecordFieldEnum.status.name()};
-        Object[] values = new Object[]{lastPrepareTime, ExamRecordStatusEnum.RESUME_PREPARE};
-        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
-        //更新场次-考试记录缓存
-        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
-                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
-                        ExamRecordCacheUtil.getStatus(recordId)));
+        boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
+                SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
+        if (lock) {
+            try {
+                Long lastPrepareTime = System.currentTimeMillis();
+                ExamRecordCacheUtil.setLastPrepareTime(recordId, lastPrepareTime, false);
+                ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.RESUME_PREPARE, false);
+                String[] columns = new String[]{ExamRecordFieldEnum.last_prepare_time.name(),
+                        ExamRecordFieldEnum.status.name()};
+                Object[] values = new Object[]{lastPrepareTime, ExamRecordStatusEnum.RESUME_PREPARE};
+                toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                //更新场次-考试记录缓存
+                ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
+                        new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                ExamRecordCacheUtil.getStatus(recordId)));
+            } finally {
+                if (Objects.nonNull(recordId)) {
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                }
+            }
+        } else {
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
         return ret;
     }
 
@@ -914,7 +939,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public ExamFinishBean finish(Long studentId, Long recordId, String type, Integer durationSeconds) {
-        String lockKey = SystemConstant.REDIS_LOCK_FINISH_EXXAM_PREFIX + recordId;
+        String lockKey = SystemConstant.REDIS_LOCK_FINISH_EXAM_PREFIX + recordId;
         Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
         if (!lock) {
             Date now = new Date();
@@ -1004,21 +1029,33 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         } else {
             ret.setStatus(FinishExamResultEnum.NORMAL);
         }
-        Long finishTime = System.currentTimeMillis();
-        ExamRecordCacheUtil.setFinishTime(recordId, finishTime, false);
-        ExamRecordCacheUtil.setDurationSeconds(recordId, durationSeconds, false);
-        ExamRecordCacheUtil.setFinishType(recordId, FinishTypeEnum.valueOf(type), false);
-        ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.FINISHED, false);
-        String[] columns = new String[]{ExamRecordFieldEnum.finish_time.name(),
-                ExamRecordFieldEnum.duration_seconds.name(), ExamRecordFieldEnum.finish_type.name(),
-                ExamRecordFieldEnum.status.name()};
-        Object[] values = new Object[]{finishTime, durationSeconds, FinishTypeEnum.valueOf(type),
-                ExamRecordStatusEnum.FINISHED};
-        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
-        //更新场次-考试记录缓存
-        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
-                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
-                        ExamRecordCacheUtil.getStatus(recordId)));
+        boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
+                SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
+        if (lock) {
+            try {
+                Long finishTime = System.currentTimeMillis();
+                ExamRecordCacheUtil.setFinishTime(recordId, finishTime, false);
+                ExamRecordCacheUtil.setDurationSeconds(recordId, durationSeconds, false);
+                ExamRecordCacheUtil.setFinishType(recordId, FinishTypeEnum.valueOf(type), false);
+                ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.FINISHED, false);
+                String[] columns = new String[]{ExamRecordFieldEnum.finish_time.name(),
+                        ExamRecordFieldEnum.duration_seconds.name(), ExamRecordFieldEnum.finish_type.name(),
+                        ExamRecordFieldEnum.status.name()};
+                Object[] values = new Object[]{finishTime, durationSeconds, FinishTypeEnum.valueOf(type),
+                        ExamRecordStatusEnum.FINISHED};
+                toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                //更新场次-考试记录缓存
+                ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
+                        new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                ExamRecordCacheUtil.getStatus(recordId)));
+            } finally {
+                if (Objects.nonNull(recordId)) {
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                }
+            }
+        } else {
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
         //更新未完成考试记录id
         TEStudentCacheDto teStudentCacheDto = (TEStudentCacheDto) redisUtil.getStudent(es.getStudentId());
         ExamingDataCacheUtil.deleteUnFinishedRecordId(studentId);

+ 19 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/TGErrorServiceImpl.java

@@ -0,0 +1,19 @@
+package com.qmth.themis.business.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.themis.business.dao.TGErrorMapper;
+import com.qmth.themis.business.entity.TGError;
+import com.qmth.themis.business.service.TGErrorService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Description: 全局异常错误信息 服务实现类
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/11/17
+ */
+@Service
+public class TGErrorServiceImpl extends ServiceImpl<TGErrorMapper, TGError> implements TGErrorService {
+
+}

+ 99 - 85
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -1,6 +1,7 @@
 package com.qmth.themis.business.service.impl;
 
 import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -15,6 +16,7 @@ import com.qmth.themis.business.dao.TOeExamRecordMapper;
 import com.qmth.themis.business.dto.MqDto;
 import com.qmth.themis.business.dto.response.MarkResultDto;
 import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
+import com.qmth.themis.business.entity.TGError;
 import com.qmth.themis.business.entity.TOeExamAnswer;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.enums.*;
@@ -89,6 +91,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Resource
     MqUtil mqUtil;
 
+    @Resource
+    TGErrorService tgErrorService;
+
     /**
      * 获取考试未完列表
      *
@@ -142,7 +147,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Transactional
     @Override
     public Long saveByPrepare(Long examId, Long examActivityId, Long examStudentId, Long paperId,
-            Integer serialNumber) {
+                              Integer serialNumber) {
         ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(examActivityId);
         ExamCacheBean exam = examService.getExamCacheBean(examId);
         TOeExamRecord er = new TOeExamRecord();
@@ -442,13 +447,13 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
 
     @Override
     public void saveFaceVerify(ExamTypeEnum type, Long recordId, Long entryAuthenticationId,
-            VerifyExceptionEnum entryAuthenticationResult) {
+                               VerifyExceptionEnum entryAuthenticationResult) {
         if (ExamTypeEnum.FIRST_START.equals(type)) {
             ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId, false);
             ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult, false);
-            String[] columns = new String[] { ExamRecordFieldEnum.entry_authentication_id.name(),
-                    ExamRecordFieldEnum.entry_authentication_result.name() };
-            Object[] values = new Object[] { entryAuthenticationId, entryAuthenticationResult };
+            String[] columns = new String[]{ExamRecordFieldEnum.entry_authentication_id.name(),
+                    ExamRecordFieldEnum.entry_authentication_result.name()};
+            Object[] values = new Object[]{entryAuthenticationId, entryAuthenticationResult};
             this.dataUpdatesMq(recordId, columns, values);
         } else if (ExamTypeEnum.IN_PROCESS.equals(type)) {
 
@@ -457,13 +462,13 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
 
     @Override
     public void saveLivenessVerify(LivenessTypeEnum type, Long recordId, Long entryAuthenticationId,
-            VerifyExceptionEnum entryAuthenticationResult) {
+                                   VerifyExceptionEnum entryAuthenticationResult) {
         if (LivenessTypeEnum.FIRST_START.equals(type)) {
             ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId, false);
             ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult, false);
-            String[] columns = new String[] { ExamRecordFieldEnum.entry_authentication_id.name(),
-                    ExamRecordFieldEnum.entry_authentication_result.name() };
-            Object[] values = new Object[] { entryAuthenticationId, entryAuthenticationResult };
+            String[] columns = new String[]{ExamRecordFieldEnum.entry_authentication_id.name(),
+                    ExamRecordFieldEnum.entry_authentication_result.name()};
+            Object[] values = new Object[]{entryAuthenticationId, entryAuthenticationResult};
             this.dataUpdatesMq(recordId, columns, values);
         } else if (LivenessTypeEnum.IN_PROCESS.equals(type)) {
             Integer count = ExamRecordCacheUtil.getInProcessLivenessVerifyCount(recordId);
@@ -570,9 +575,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListBean> invigilatePageList(IPage<Map> iPage, Long examId, Long examActivityId,
-            String roomCode, Integer paperDownload, String status, String name, String identity,
-            Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource,
-            Long userId) {
+                                                        String roomCode, Integer paperDownload, String status, String name, String identity,
+                                                        Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource,
+                                                        Long userId) {
         return tOeExamRecordMapper
                 .invigilatePageList(iPage, examId, examActivityId, roomCode, paperDownload, status, name, identity,
                         minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
@@ -598,8 +603,8 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListVideoBean> invigilatePageListVideo(IPage<Map> iPage, Long examId, Long examActivityId,
-            String roomCode, Integer paperDownload, String status, String name, String identity,
-            Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, Long userId, Long orgId) {
+                                                                  String roomCode, Integer paperDownload, String status, String name, String identity,
+                                                                  Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePageListVideo(iPage, examId, examActivityId, roomCode, paperDownload, status, name, identity,
                         minWarningCount, maxWarningCount, clientWebsocketStatus, userId, orgId);
@@ -616,7 +621,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public List<InvigilateListVideoBean> invigilatePageListVideoRandom(Long examId, Long userId, Integer randomNum,
-            Long orgId) {
+                                                                       Long orgId) {
         return tOeExamRecordMapper.invigilatePageListVideoRandom(examId, userId, randomNum, orgId);
     }
 
@@ -643,9 +648,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListPatrolBean> invigilatePagePatrolList(IPage<Map> iPage, Long examId, Long examActivityId,
-            String roomCode, String status, String name, String identity, Integer minMultipleFaceCount,
-            Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount,
-            Integer maxWarningCount, String clientWebsocketStatus, Long userId, Long orgId) {
+                                                                    String roomCode, String status, String name, String identity, Integer minMultipleFaceCount,
+                                                                    Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount,
+                                                                    Integer maxWarningCount, String clientWebsocketStatus, Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePagePatrolList(iPage, examId, examActivityId, roomCode, status, name, identity,
                         minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
@@ -674,9 +679,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListWarningBean> invigilatePageWarningList(IPage<Map> iPage, Long examId,
-            Long examActivityId, String roomCode, Integer approveStatus, String name, String identity,
-            Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount,
-            Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, Long userId, Long orgId) {
+                                                                      Long examActivityId, String roomCode, Integer approveStatus, String name, String identity,
+                                                                      Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount,
+                                                                      Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePageWarningList(iPage, examId, examActivityId, roomCode, approveStatus, name, identity,
                         minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
@@ -704,9 +709,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public Integer approveStatusListUpdate(Long examId, Long examActivityId, String roomCode, Integer approveStatus,
-            String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
-            Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
-            Long userId, Long orgId) {
+                                           String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
+                                           Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
+                                           Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .approveStatusListUpdate(examId, examActivityId, roomCode, approveStatus, name, identity,
                         minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
@@ -728,8 +733,8 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListProgressBean> invigilatePageProgressList(IPage<Map> iPage, Long examId,
-            Long examActivityId, String roomCode, String courseCode, String name, String identity, Long userId,
-            Long orgId) {
+                                                                        Long examActivityId, String roomCode, String courseCode, String name, String identity, Long userId,
+                                                                        Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePageProgressList(iPage, examId, examActivityId, roomCode, courseCode, name, identity, userId,
                         orgId);
@@ -750,7 +755,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public List<InvigilateListProgressExcelBean> invigilatePageProgressListExport(Long examId, Long examActivityId,
-            String roomCode, String courseCode, String name, String identity, Long userId, Long orgId) {
+                                                                                  String roomCode, String courseCode, String name, String identity, Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePageProgressListExport(examId, examActivityId, roomCode, courseCode, name, identity, userId,
                         orgId);
@@ -781,10 +786,10 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public IPage<InvigilateListHistoryBean> invigilatePageListHistory(IPage<Map> iPage, Long examId,
-            Long examActivityId, String roomCode, String courseCode, String status, Integer breachStatus,
-            String finishType, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
-            Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
-            Long userId, Long orgId) {
+                                                                      Long examActivityId, String roomCode, String courseCode, String status, Integer breachStatus,
+                                                                      String finishType, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
+                                                                      Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
+                                                                      Long userId, Long orgId) {
         return tOeExamRecordMapper
                 .invigilatePageListHistory(iPage, examId, examActivityId, roomCode, courseCode, status, breachStatus,
                         finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount,
@@ -946,7 +951,7 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     public TOeExamRecord findMarkResult(MarkResultDto markResultDto,
-            RecordSelectStrategyEnum recordSelectStrategyEnum) {
+                                        RecordSelectStrategyEnum recordSelectStrategyEnum) {
         TOeExamRecord tOeExamRecord = null;
         Double sumScore = null;
         //客观分最高
@@ -1011,12 +1016,14 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      * 设置断点信息
      *
      * @param recordId
+     * @param count
      */
     @Override
     @Transactional
-    public void setExamBreak(Long recordId) {
-        if (redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_BREAK_PREFIX + recordId,
-                SystemConstant.REDIS_LOCK_EXAM_BREAK_TIME_OUT)) {
+    public Boolean setExamBreak(Long recordId, Integer count) {
+        boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
+                SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
+        if (lock) {
             try {
                 Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ?
                         0 :
@@ -1031,11 +1038,11 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                 ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount, false);
                 Long lastStartTime = System.currentTimeMillis();
                 ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
-                String[] columns = new String[] { ExamRecordFieldEnum.last_break_id.name(),
+                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 };
+                        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
@@ -1101,71 +1108,78 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                 }
             } finally {
                 if (Objects.nonNull(recordId)) {
-                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_BREAK_PREFIX + recordId);
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
                 }
             }
+        } else {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            if (count >= 20) {
+                JSONObject jsonObject = new JSONObject();
+                jsonObject.put("recordId", recordId);
+                jsonObject.put("method", "setExamBreak");
+                jsonObject.put("examStatus", ExamRecordCacheUtil.getStatus(recordId));
+                TGError tgError = new TGError(jsonObject.toJSONString(), System.currentTimeMillis());
+                tgErrorService.save(tgError);
+                return false;
+            } else {
+                count++;
+                setExamBreak(recordId, count);
+            }
         }
+        return true;
     }
 
     /**
      * 发送断点信息
      *
      * @param recordId
-     * @param setBreak
      * @return
      */
     @Override
     public Boolean sendExamBreakMsg(Long recordId, boolean setBreak) {
         Boolean finished = false;
-        if (redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_BREAK_LOGIC_PREFIX + recordId,
-                SystemConstant.REDIS_LOCK_EXAM_BREAK_LOGIC_TIME_OUT)) {
-            try {
-                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);
-                if (setBreak) {//如果需要断点,则次数本地先加1,可以避免多生成考试记录
-                    alreadyBreakCount++;
-                }
-                Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
-                if (Objects.nonNull(lastBreakTime)
-                        && (System.currentTimeMillis() - lastBreakTime) / 1000 >= breakExpireSeconds) {
+        try {
+            if (setBreak) {
+                this.setExamBreak(recordId, 0);
+            }
+            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 (leftBreakResumeCount < 0) {
-                        finished = true;
-                        examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(),
-                                durationSeconds);
-                    } else {
-                        if (setBreak) {
-                            this.setExamBreak(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_LOGIC_PREFIX + recordId);
                 }
             }
+        } catch (Exception e) {
+            log.error("请求出错", e);
+            if (e instanceof BusinessException) {
+                throw new BusinessException(e.getMessage());
+            } else {
+                throw new RuntimeException(e);
+            }
         }
         return finished;
     }

+ 5 - 0
themis-business/src/main/resources/mapper/TGErrorMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.themis.business.dao.TGErrorMapper">
+
+</mapper>

+ 3 - 1
themis-common/src/main/java/com/qmth/themis/common/enums/ExceptionResultEnum.java

@@ -237,7 +237,9 @@ public enum ExceptionResultEnum {
 
     FINISH_TYPE_ERROR(500, 500018, "考试结束类型错误"),
 
-    EXAM_STUDENT_ENABLE(500, 500019, "考生已停用");
+    EXAM_STUDENT_ENABLE(500, 500019, "考生已停用"),
+
+    EXAM_STATUS_UPDATE_ERROR(500, 5000020, "考试状态更新失败");
 
     private int statusCode;
     private int code;

+ 1 - 2
themis-exam/src/main/java/com/qmth/themis/exam/api/TEStudentController.java

@@ -265,9 +265,8 @@ public class TEStudentController {
                     .equals(status, ExamRecordStatusEnum.RESUME_PREPARE)) {
                 //只有ANSWERING状态才生成断点
                 if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
-                    //先生成断点,再比较
                     ExamConstant.sendExamStopMsg(recordId, true);
-                    tOeExamRecordService.setExamBreak(recordId);
+                    tOeExamRecordService.setExamBreak(recordId, 0);
                 }
                 Boolean finished = tOeExamRecordService.sendExamBreakMsg(recordId, false);
                 if (finished) {

+ 2 - 4
themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketOeServer.java

@@ -37,7 +37,6 @@ import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -123,12 +122,11 @@ public class WebSocketOeServer implements Concurrently {
             //从set中删除
             subOnlineCount();
             //判断是否是正常退出
-            Date now = new Date();
             ExamRecordCacheUtil.setClientWebsocketStatus(recordId, WebsocketStatusEnum.OFF_LINE, true);
             ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(this.recordId);
-            if (!Objects.equals(status, ExamRecordStatusEnum.FIRST_PREPARE) && !Objects.equals(status, ExamRecordStatusEnum.FINISHED) && !Objects.equals(status, ExamRecordStatusEnum.PERSISTED)) {
+            if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
                 //大于等于超时时间,说明规定时间内都没有通信,非正常退出,因为期间会有心跳更新updateTime
-                if ((now.getTime() - this.updateTime) / 1000 >= SystemConstant.WEBSOCKET_MAX_TIME_OUT / 1000) {
+                if ((System.currentTimeMillis() - this.updateTime) / 1000 >= SystemConstant.WEBSOCKET_MAX_TIME_OUT / 1000) {
                     log.info("超时退出");
                     //发送延时mq消息start
                     MqDtoService mqDtoService = SpringContextHolder.getBean(MqDtoService.class);

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

@@ -265,10 +265,8 @@ public class MqLogicServiceImpl implements MqLogicService {
         Long recordId = Long.parseLong(String.valueOf(tranMap.get("recordId")));
         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))) {
+        if (Objects.nonNull(websocketStatusEnum) && (Objects.equals(websocketStatusEnum, WebsocketStatusEnum.OFF_LINE)
+                || Objects.equals(status, ExamRecordStatusEnum.ANSWERING))) {
             examRecordService.sendExamBreakMsg(recordId, true);
             //更新客户端摄像头推流状态为stop
             MonitorStatusSourceEnum cameraStatusSourceEnum = ExamRecordCacheUtil