Преглед изворни кода

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

wangliang пре 4 година
родитељ
комит
f8e22db79c

+ 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> {
+
+}

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

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

+ 77 - 48
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -508,27 +508,32 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         ret.setPaperDecryptSecret(ep.getDecryptSecret());
         ret.setPaperDecryptVector(ep.getDecryptVector());
 
-        if (!ExamRecordStatusEnum.RESUME_PREPARE.equals(sta)) {//非断点进入的
-            // 写入次数
-            es.setAlreadyExamCount(es.getAlreadyExamCount() + 1);
-            // 更新考生缓存
-            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)));
+        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 (Objects.nonNull(exam.getForceFinish()) && exam.getForceFinish().intValue() == 0) {
@@ -851,17 +856,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;
     }
 
@@ -911,7 +928,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();
@@ -1001,21 +1018,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 {
+
+}

+ 70 - 56
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.*;
@@ -85,6 +87,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Resource
     UidUtil uidUtil;
 
+    @Resource
+    TGErrorService tgErrorService;
+
     /**
      * 获取考试未完列表
      *
@@ -1007,12 +1012,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 :
@@ -1027,11 +1034,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
@@ -1047,14 +1054,14 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                 Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ?
                         0 :
                         ec.getBreakExpireSeconds();
-                if (breakExpireSeconds > 0) {
+                if (breakExpireSeconds.intValue() > 0) {
                     List<String> list = SystemConstant.mqDelayLevelList.subList(5, 15);
                     String level = null;
-                    if (breakExpireSeconds <= 60) {
+                    if (breakExpireSeconds.intValue() <= 60) {
                         level = "1m";
                     } else {
                         Integer time = breakExpireSeconds.intValue() / 60;
-                        if (time >= 30) {
+                        if (time.intValue() >= 30) {
                             level = "30m";
                         } else {
                             for (String s : list) {
@@ -1097,71 +1104,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

@@ -241,7 +241,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;
 

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

@@ -100,12 +100,12 @@ public class TEStudentController {
 
     @ApiOperation(value = "学生登录接口")
     @RequestMapping(value = "/login", method = RequestMethod.POST)
-    @ApiResponses({ @ApiResponse(code = 200, message = "学生信息", response = TEExamResultDto.class) })
+    @ApiResponses({@ApiResponse(code = 200, message = "学生信息", response = TEExamResultDto.class)})
     public Result login(
-            @ApiJsonObject(name = "loginStudent", value = { @ApiJsonProperty(key = "identity", description = "证件号"),
+            @ApiJsonObject(name = "loginStudent", value = {@ApiJsonProperty(key = "identity", description = "证件号"),
                     @ApiJsonProperty(key = "password", description = "密码"),
                     @ApiJsonProperty(key = "orgId", type = "long", example = "1", description = "机构id"),
-                    @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id") }) @ApiParam(value = "学生信息", required = true) @RequestBody Map<String, Object> mapParameter)
+                    @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id")}) @ApiParam(value = "学生信息", required = true) @RequestBody Map<String, Object> mapParameter)
             throws NoSuchAlgorithmException {
         if (Objects.isNull(mapParameter)) {
             throw new BusinessException(ExceptionResultEnum.STUDENT_IS_NULL);
@@ -260,7 +260,7 @@ public class TEStudentController {
                 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) {
@@ -285,7 +285,7 @@ public class TEStudentController {
 
     @ApiOperation(value = "登出接口")
     @RequestMapping(value = "/logout", method = RequestMethod.POST)
-    @ApiResponses({ @ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class) })
+    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
     public Result logout() throws NoSuchAlgorithmException {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         TBSession tbSession = (TBSession) ServletUtil.getRequestSession();
@@ -328,7 +328,7 @@ public class TEStudentController {
      * @return
      */
     private ExamUnFinishBean unFinishCommon(Long recordId, ExamCacheBean ec, ExamStudentCacheBean examStudentCacheBean,
-            ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
+                                            ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
         ExamCourseCacheBean examCourseCacheBean = teExamCourseService
                 .getExamCourseCacheBean(ec.getId(), examStudentCacheBean.getCourseCode());
         TEExamActivityDto teExamActivityDto = new TEExamActivityDto(ec, examActivityCacheBean, examStudentCacheBean,

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

@@ -209,14 +209,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

@@ -261,10 +261,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