Forráskód Böngészése

Merge remote-tracking branch 'origin/dev_20201109' into dev_20201109

# Conflicts:
#	themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java
luoshi 4 éve
szülő
commit
5526b1c0ff
25 módosított fájl, 774 hozzáadás és 438 törlés
  1. 51 25
      themis-business/src/main/java/com/qmth/themis/business/cache/ExamBreakCacheUtil.java
  2. 4 0
      themis-business/src/main/java/com/qmth/themis/business/cache/ExamRecordCacheUtil.java
  3. 5 7
      themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java
  4. 18 0
      themis-business/src/main/java/com/qmth/themis/business/dao/TGErrorMapper.java
  5. 76 0
      themis-business/src/main/java/com/qmth/themis/business/entity/TGError.java
  6. 39 0
      themis-business/src/main/java/com/qmth/themis/business/enums/ExamBreakHistoryFieldEnum.java
  7. 3 1
      themis-business/src/main/java/com/qmth/themis/business/enums/MqTagEnum.java
  8. 15 0
      themis-business/src/main/java/com/qmth/themis/business/service/TGErrorService.java
  9. 26 6
      themis-business/src/main/java/com/qmth/themis/business/service/TOeExamBreakHistoryService.java
  10. 2 2
      themis-business/src/main/java/com/qmth/themis/business/service/TOeExamRecordService.java
  11. 179 70
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java
  12. 19 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TGErrorServiceImpl.java
  13. 64 20
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamBreakHistoryServiceImpl.java
  14. 171 159
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java
  15. 0 10
      themis-business/src/main/resources/mapper/TEExamReexamMapper.xml
  16. 5 0
      themis-business/src/main/resources/mapper/TGErrorMapper.xml
  17. 6 7
      themis-business/src/main/resources/mapper/TIeInvigilateWarnInfoMapper.xml
  18. 42 113
      themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml
  19. 7 1
      themis-common/src/main/java/com/qmth/themis/common/enums/ExceptionResultEnum.java
  20. 10 2
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java
  21. 0 1
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEStudentController.java
  22. 3 3
      themis-exam/src/main/java/com/qmth/themis/exam/config/ExamConstant.java
  23. 2 4
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketOeServer.java
  24. 26 6
      themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java
  25. 1 1
      themis-task/src/main/java/com/qmth/themis/task/start/StartRunning.java

+ 51 - 25
themis-business/src/main/java/com/qmth/themis/business/cache/ExamBreakCacheUtil.java

@@ -1,8 +1,10 @@
 package com.qmth.themis.business.cache;
 
 import com.qmth.themis.business.constant.SpringContextHolder;
+import com.qmth.themis.business.enums.ExamBreakHistoryFieldEnum;
 import com.qmth.themis.business.enums.ExceptionEnum;
 import com.qmth.themis.business.enums.VerifyExceptionEnum;
+import com.qmth.themis.business.service.TOeExamBreakHistoryService;
 import com.qmth.themis.business.util.RedisUtil;
 
 /**
@@ -13,71 +15,95 @@ import com.qmth.themis.business.util.RedisUtil;
  * @Long: 2020-07-29
  */
 public class ExamBreakCacheUtil {
+    private static TOeExamBreakHistoryService tOeExamBreakHistoryService = SpringContextHolder.getBean(TOeExamBreakHistoryService.class);
     private static RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
 
-    public static void setExamRecordId(Long examBreakId, Long examRecordId) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "examRecordId", examRecordId);
+    public static void setExamRecordId(Long examBreakId, Long examRecordId, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.exam_record_id.getCode(), examRecordId);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.exam_record_id.name(), examRecordId);
+        }
     }
 
     public static Long getExamRecordId(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "examRecordId");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.exam_record_id.getCode());
     }
 
-    public static void setBreakTime(Long examBreakId, Long breakTime) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "breakTime", breakTime);
+    public static void setBreakTime(Long examBreakId, Long breakTime, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.break_time.getCode(), breakTime);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.break_time.name(), breakTime);
+        }
     }
 
     public static Long getBreakTime(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "breakTime");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.break_time.getCode());
     }
 
-    public static void setBreakReason(Long examBreakId, ExceptionEnum breakReason) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "breakReason", breakReason);
+    public static void setBreakReason(Long examBreakId, ExceptionEnum breakReason, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.break_reason.getCode(), breakReason);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.break_reason.name(), breakReason.name());
+        }
     }
 
     public static ExceptionEnum getBreakReason(Long examBreakId) {
-        return (ExceptionEnum) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "breakReason");
+        return (ExceptionEnum) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.break_reason.getCode());
     }
 
-    public static void setResumeReason(Long examBreakId, String resumeReason) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "resumeReason", resumeReason);
+    public static void setResumeReason(Long examBreakId, String resumeReason, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.resume_reason.getCode(), resumeReason);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.resume_reason.name(), resumeReason);
+        }
     }
 
     public static Long getResumeReason(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "resumeReason");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.resume_reason.getCode());
     }
 
 
-    public static void setPrepareTime(Long examBreakId, Long prepareTime) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "prepareTime", prepareTime);
+    public static void setPrepareTime(Long examBreakId, Long prepareTime, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.prepare_time.getCode(), prepareTime);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.prepare_time.name(), prepareTime);
+        }
     }
 
     public static Long getPrepareTime(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "prepareTime");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.prepare_time.getCode());
     }
 
-
-    public static void setStartTime(Long examBreakId, Long startTime) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "startTime", startTime);
+    public static void setStartTime(Long examBreakId, Long startTime, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.start_time.getCode(), startTime);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.start_time.name(), startTime);
+        }
     }
 
     public static Long getStartTime(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "startTime");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.start_time.getCode());
     }
 
-    public static void setEntryAuthenticationResult(Long examBreakId, VerifyExceptionEnum entryAuthenticationResult) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "entryAuthenticationResult", entryAuthenticationResult);
+    public static void setEntryAuthenticationResult(Long examBreakId, VerifyExceptionEnum entryAuthenticationResult, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.entry_authentication_result.getCode(), entryAuthenticationResult);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.entry_authentication_result.name(), entryAuthenticationResult.name());
+        }
     }
 
     public static VerifyExceptionEnum getEntryAuthenticationResult(Long examBreakId) {
-        return (VerifyExceptionEnum) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "entryAuthenticationResult");
+        return (VerifyExceptionEnum) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.entry_authentication_result.getCode());
     }
 
-    public static void setEntryAuthenticationId(Long examBreakId, Long entryAuthenticationId) {
-        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), "entryAuthenticationId", entryAuthenticationId);
+    public static void setEntryAuthenticationId(Long examBreakId, Long entryAuthenticationId, boolean update) {
+        redisUtil.set(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.entry_authentication_id.getCode(), entryAuthenticationId);
+        if (update) {
+            tOeExamBreakHistoryService.dataUpdateMq(examBreakId, ExamBreakHistoryFieldEnum.entry_authentication_id.name(), entryAuthenticationId);
+        }
     }
 
     public static Long getEntryAuthenticationId(Long examBreakId) {
-        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), "entryAuthenticationId");
+        return (Long) redisUtil.get(RedisKeyHelper.examBreakCacheKey(examBreakId), ExamBreakHistoryFieldEnum.entry_authentication_id.getCode());
     }
 }

+ 4 - 0
themis-business/src/main/java/com/qmth/themis/business/cache/ExamRecordCacheUtil.java

@@ -46,6 +46,10 @@ public class ExamRecordCacheUtil {
         }
     }
 
+    public static Long getFirstStartTime(Long recordId) {
+        return (Long) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.first_start_time.getCode());
+    }
+
     public static Integer getDurationSeconds(Long recordId) {
         return (Integer) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.duration_seconds.getCode());
     }

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

@@ -125,6 +125,8 @@ public class SystemConstant {
 
     public static final int MAX_EXPORT_SIZE = 500;
 
+    public static final int MAX_EXAM_STATUS_COUNT = 20;
+
     public static final String EXCEL_PREFIX = ".xlsx";
 
     public static final String TXT_PREFIX = ".txt";
@@ -171,9 +173,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 +185,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 +209,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;
+    }
+}

+ 39 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/ExamBreakHistoryFieldEnum.java

@@ -0,0 +1,39 @@
+package com.qmth.themis.business.enums;
+
+/**
+ * @Description: 断点记录字段
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/9/18
+ */
+public enum ExamBreakHistoryFieldEnum {
+
+    id("id"),
+
+    exam_record_id("examRecordId"),
+
+    break_time("breakTime"),
+
+    break_reason("breakReason"),
+
+    resume_reason("resumeReason"),
+
+    prepare_time("prepareTime"),
+
+    start_time("startTime"),
+
+    entry_authentication_result("entryAuthenticationResult"),
+
+    entry_authentication_id("entryAuthenticationId");
+
+    private String code;
+
+    private ExamBreakHistoryFieldEnum(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+}

+ 3 - 1
themis-business/src/main/java/com/qmth/themis/business/enums/MqTagEnum.java

@@ -48,7 +48,9 @@ public enum MqTagEnum {
     EXAM_STUDENT_EXPORT("考生导出任务标签", "考生导出任务", "normal", 36),
     MARK_RESULT_SIMPLE_EXPORT("成绩查询简版导出任务标签", "成绩查询简版导出任务", "normal", 37),
     MARK_RESULT_STANDARD_EXPORT("成绩查询标准版导出任务标签", "成绩查询标准版导出任务", "normal", 38),
-    EXAM_BREAK_DELAY("断点时间标签", "断点时间", "delay", 39);
+    EXAM_BREAK_DELAY("断点时间标签", "断点时间", "delay", 39),
+    EXAM_BREAK_HISTORY_UPDATE("断点记录数据更新标签", "断点记录数据更新", "normal", 40),
+    EXAM_BREAK_HISTORY_UPDATE_COLUMNS("断点记录多字段数据更新标签", "断点记录多字段数据更新", "normal", 41);
 
     private MqTagEnum(String desc, String code, String type, int id) {
         this.desc = desc;

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

+ 26 - 6
themis-business/src/main/java/com/qmth/themis/business/service/TOeExamBreakHistoryService.java

@@ -13,11 +13,31 @@ import com.qmth.themis.business.enums.VerifyExceptionEnum;
  */
 public interface TOeExamBreakHistoryService extends IService<TOeExamBreakHistory> {
 
-	/**保存验证信息
-	 * @param recordId
-	 * @param entryAuthenticationId
-	 * @param entryAuthenticationResult
-	 */
-	void saveAuthenticationResult(Long recordId, Long entryAuthenticationId, VerifyExceptionEnum entryAuthenticationResult);
+    /**
+     * 保存验证信息
+     *
+     * @param recordId
+     * @param entryAuthenticationId
+     * @param entryAuthenticationResult
+     */
+    void saveAuthenticationResult(Long recordId, Long entryAuthenticationId, VerifyExceptionEnum entryAuthenticationResult);
 
+
+    /**
+     * 断点记录字段同步消息发送
+     *
+     * @param breakId
+     * @param colName
+     * @param colValue
+     */
+    void dataUpdateMq(Long breakId, String colName, Object colValue);
+
+    /**
+     * 考试记录多字段更新
+     *
+     * @param breakId
+     * @param colNames
+     * @param colValues
+     */
+    void dataUpdatesMq(Long breakId, String[] colNames, Object[] colValues);
 }

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

@@ -70,7 +70,7 @@ public interface TOeExamRecordService extends IService<TOeExamRecord> {
     void dataUpdate(Long recordId, String colName, Object colValue);
 
     /**
-     * 考试记录字段同步消息发送
+     * 考试记录多字段更新
      *
      * @param recordId
      * @param colNames
@@ -409,7 +409,7 @@ public interface TOeExamRecordService extends IService<TOeExamRecord> {
      *
      * @param recordId
      */
-    void setExamBreak(Long recordId);
+    Boolean setExamBreak(Long recordId);
 
     /**
      * 发送断点信息

+ 179 - 70
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -7,10 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.bean.admin.OpenExamBean;
 import com.qmth.themis.business.bean.exam.*;
-import com.qmth.themis.business.cache.ExamActivityRecordCacheUtil;
-import com.qmth.themis.business.cache.ExamRecordCacheUtil;
-import com.qmth.themis.business.cache.ExamingDataCacheUtil;
-import com.qmth.themis.business.cache.RedisKeyHelper;
+import com.qmth.themis.business.cache.*;
 import com.qmth.themis.business.cache.bean.*;
 import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
@@ -20,10 +17,7 @@ import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
 import com.qmth.themis.business.dto.response.TEExamActivityDto;
 import com.qmth.themis.business.dto.response.TEExamDto;
 import com.qmth.themis.business.dto.response.TEExamQueryDto;
-import com.qmth.themis.business.entity.TBSession;
-import com.qmth.themis.business.entity.TBTaskHistory;
-import com.qmth.themis.business.entity.TEExam;
-import com.qmth.themis.business.entity.TOeExamRecord;
+import com.qmth.themis.business.entity.*;
 import com.qmth.themis.business.enums.*;
 import com.qmth.themis.business.service.*;
 import com.qmth.themis.business.util.*;
@@ -103,6 +97,9 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
     @Resource
     MqUtil mqUtil;
 
+    @Resource
+    TGErrorService tgErrorService;
+
     /**
      * 查询考试批次
      *
@@ -119,7 +116,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public IPage<TEExamQueryDto> examQuery(IPage<Map> iPage, Long userId, Long id, String code, String name,
-            String mode, Integer enable, Long orgId, String type) {
+                                           String mode, Integer enable, Long orgId, String type) {
         return teExamMapper.examQuery(iPage, userId, id, code, name, mode, enable, orgId, type);
     }
 
@@ -283,12 +280,12 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 ExamRecordCacheUtil.setMinDurationSeconds(recordId, examCache.getMinDurationSeconds(), false);
                 ExamRecordCacheUtil.setMaxDurationSeconds(recordId, ac.getMaxDurationSeconds(), false);
                 ExamRecordCacheUtil.setForceFinish(recordId, examCache.getForceFinish(), false);
-                String[] columns = new String[] { ExamRecordFieldEnum.start_time.name(),
+                String[] columns = new String[]{ExamRecordFieldEnum.start_time.name(),
                         ExamRecordFieldEnum.end_time.name(), ExamRecordFieldEnum.opening_seconds.name(),
                         ExamRecordFieldEnum.min_duration_seconds.name(),
-                        ExamRecordFieldEnum.max_duration_seconds.name(), ExamRecordFieldEnum.force_finish.name() };
-                Object[] values = new Object[] { ac.getStartTime(), ac.getFinishTime(), ac.getOpeningSeconds(),
-                        examCache.getMinDurationSeconds(), ac.getMaxDurationSeconds(), examCache.getForceFinish() };
+                        ExamRecordFieldEnum.max_duration_seconds.name(), ExamRecordFieldEnum.force_finish.name()};
+                Object[] values = new Object[]{ac.getStartTime(), ac.getFinishTime(), ac.getOpeningSeconds(),
+                        examCache.getMinDurationSeconds(), ac.getMaxDurationSeconds(), examCache.getForceFinish()};
                 toeExamRecordService.dataUpdatesMq(recordId, columns, values);
                 return prepare;
             }
@@ -495,14 +492,14 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 if (Objects.isNull(clientCameraStatus) || Objects
                         .equals(MonitorStatusSourceEnum.STOP, clientCameraStatus)) {
                     switch (strs[i]) {
-                    case "CLIENT_CAMERA":
-                        throw new BusinessException(ExceptionResultEnum.CLIENT_CAMERA_OFFLINE);
-                    case "CLIENT_SCREEN":
-                        throw new BusinessException(ExceptionResultEnum.CLIENT_SCREEN_OFFLINE);
-                    case "MOBILE_FIRST":
-                        throw new BusinessException(ExceptionResultEnum.MOBILE_FIRST_OFFLINE);
-                    default:
-                        throw new BusinessException(ExceptionResultEnum.MOBILE_SECOND_OFFLINE);
+                        case "CLIENT_CAMERA":
+                            throw new BusinessException(ExceptionResultEnum.CLIENT_CAMERA_OFFLINE);
+                        case "CLIENT_SCREEN":
+                            throw new BusinessException(ExceptionResultEnum.CLIENT_SCREEN_OFFLINE);
+                        case "MOBILE_FIRST":
+                            throw new BusinessException(ExceptionResultEnum.MOBILE_FIRST_OFFLINE);
+                        default:
+                            throw new BusinessException(ExceptionResultEnum.MOBILE_SECOND_OFFLINE);
                     }
                 }
             }
@@ -511,6 +508,66 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         ret.setPaperDecryptSecret(ep.getDecryptSecret());
         ret.setPaperDecryptVector(ep.getDecryptVector());
 
+        boolean lock = false;
+        for (int i = 0; i < SystemConstant.MAX_EXAM_STATUS_COUNT; i++) {
+            lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
+                    SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
+            if (lock) {
+                try {
+                    // 更新考试记录缓存
+                    Long firstStartTime = ExamRecordCacheUtil.getFirstStartTime(recordId), lastStartTime = null;
+                    String[] columns = null;
+                    Object[] values = null;
+                    ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.ANSWERING, false);
+                    if (Objects.isNull(firstStartTime)) {
+                        firstStartTime = System.currentTimeMillis();
+                        ExamRecordCacheUtil.setFirstStartTime(recordId, firstStartTime, false);
+                        columns = new String[]{ExamRecordFieldEnum.first_start_time.name(),
+                                ExamRecordFieldEnum.status.name()};
+                        values = new Object[]{firstStartTime, ExamRecordStatusEnum.ANSWERING};
+                    } else {
+                        lastStartTime = System.currentTimeMillis();
+                        ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
+                        columns = new String[]{ExamRecordFieldEnum.last_start_time.name(),
+                                ExamRecordFieldEnum.status.name()};
+                        values = new Object[]{lastStartTime, ExamRecordStatusEnum.ANSWERING};
+                    }
+                    Long breakId = ExamRecordCacheUtil.getLastBreakId(recordId);
+                    ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                    if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum == ExamRecordStatusEnum.ANSWERING) {
+                        if (Objects.nonNull(breakId) && Objects.nonNull(lastStartTime)) {
+                            ExamBreakCacheUtil.setStartTime(breakId, lastStartTime, true);
+                        }
+                        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                        //更新场次-考试记录缓存
+                        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId,
+                                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                        ExamRecordCacheUtil.getStatus(recordId)));
+                    }
+                    break;
+                } finally {
+                    if (Objects.nonNull(recordId)) {
+                        redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                    }
+                }
+            } else {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (!lock) {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("recordId", recordId);
+            jsonObject.put("method", "start");
+            jsonObject.put("examStatus", ExamRecordCacheUtil.getStatus(recordId));
+            TGError tgError = new TGError(jsonObject.toJSONString(), System.currentTimeMillis());
+            tgErrorService.save(tgError);
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
+
         if (!ExamRecordStatusEnum.RESUME_PREPARE.equals(sta)) {//非断点进入的
             // 写入次数
             es.setAlreadyExamCount(es.getAlreadyExamCount() + 1);
@@ -518,20 +575,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) {
@@ -612,7 +655,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public AnswerSubmitBean answerSubmit(Long studentId, Long recordId, Integer mainNumber, Integer subNumber,
-            Integer subIndex, String answer, Long version, Integer durationSeconds) {
+                                         Integer subIndex, String answer, Long version, Integer durationSeconds) {
 
         // 校验当前登录用户和参数一致性
         if (ExamRecordCacheUtil.getId(recordId) == null) {
@@ -708,7 +751,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public AudioLeftPlayCountSubmitBean audioLeftPlayCountSubmit(Long studentId, Long recordId, String key,
-            Integer count) {
+                                                                 Integer count) {
 
         // 校验当前登录用户和参数一致性
         if (ExamRecordCacheUtil.getId(recordId) == null) {
@@ -854,17 +897,53 @@ 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 = false;
+        for (int i = 0; i < SystemConstant.MAX_EXAM_STATUS_COUNT; i++) {
+            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};
+                    ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                    Long breakId = ExamRecordCacheUtil.getLastBreakId(recordId);
+                    if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum == ExamRecordStatusEnum.RESUME_PREPARE) {
+                        if (Objects.nonNull(breakId)) {
+                            ExamBreakCacheUtil.setPrepareTime(breakId, lastPrepareTime, true);
+                        }
+                        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                        //更新场次-考试记录缓存
+                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
+                                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                        ExamRecordCacheUtil.getStatus(recordId)));
+                    }
+                    break;
+                } finally {
+                    if (Objects.nonNull(recordId)) {
+                        redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                    }
+                }
+            } else {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (!lock) {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("recordId", recordId);
+            jsonObject.put("method", "resume");
+            jsonObject.put("examStatus", ExamRecordCacheUtil.getStatus(recordId));
+            TGError tgError = new TGError(jsonObject.toJSONString(), System.currentTimeMillis());
+            tgErrorService.save(tgError);
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
         return ret;
     }
 
@@ -914,7 +993,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 +1083,53 @@ 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 = false;
+        for (int i = 0; i < SystemConstant.MAX_EXAM_STATUS_COUNT; i++) {
+            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};
+                    ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                    if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum == ExamRecordStatusEnum.FINISHED) {
+                        toeExamRecordService.dataUpdatesMq(recordId, columns, values);
+                        //更新场次-考试记录缓存
+                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId,
+                                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                        ExamRecordCacheUtil.getStatus(recordId)));
+                    }
+                    break;
+                } finally {
+                    if (Objects.nonNull(recordId)) {
+                        redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
+                    }
+                }
+            } else {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        if (!lock) {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("recordId", recordId);
+            jsonObject.put("method", "diposeFinish");
+            jsonObject.put("examStatus", ExamRecordCacheUtil.getStatus(recordId));
+            TGError tgError = new TGError(jsonObject.toJSONString(), System.currentTimeMillis());
+            tgErrorService.save(tgError);
+            throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_UPDATE_ERROR);
+        }
         //更新未完成考试记录id
         TEStudentCacheDto teStudentCacheDto = (TEStudentCacheDto) redisUtil.getStudent(es.getStudentId());
         ExamingDataCacheUtil.deleteUnFinishedRecordId(studentId);
@@ -1100,12 +1211,12 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
     @Override
     public ExamResultBean result(Long recordId) {
         ExamResultBean ret = new ExamResultBean();
+        TOeExamRecord er = null;
         Integer warningCount = null;
         Integer breachStatus = null;
         Integer hasAnswerFile = null;
-        Long examId = null;
         if (ExamRecordCacheUtil.getId(recordId) == null) {
-            TOeExamRecord er = toeExamRecordService.getById(recordId);
+            er = toeExamRecordService.getById(recordId);
             if (er == null) {
                 throw new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_RECORD);
             }
@@ -1116,7 +1227,6 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             warningCount = er.getWarningCount();
             breachStatus = er.getBreachStatus();
             hasAnswerFile = er.getHasAnswerFile();
-            examId = er.getExamId();
         } else {
             ret.setDurationSeconds(ExamRecordCacheUtil.getDurationSeconds(recordId));
             ret.setFinishTime(ExamRecordCacheUtil.getFinishTime(recordId));
@@ -1127,12 +1237,11 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             hasAnswerFile = Objects.nonNull(ExamRecordCacheUtil.getHasAnswerFile(recordId)) ?
                     ExamRecordCacheUtil.getHasAnswerFile(recordId) :
                     0;
-            examId = ExamRecordCacheUtil.getExamId(recordId);
         }
 
         //页面结果
         if (hasAnswerFile.intValue() == 1) {
-            ExamCacheBean exam = getExamCacheBeanNative(examId);
+            ExamCacheBean exam = getExamCacheBeanNative(er.getExamId());
             if (exam.getShowObjectiveScore() != null && exam.getShowObjectiveScore().intValue() == 1) {//实时出分
                 if (InvigilateVerifyEnum.NOW.equals(exam.getInvigilateVerify())) {//实时审核
                     if (warningCount != null && warningCount.intValue() > 0) {//有预警
@@ -1188,7 +1297,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public void sendOeLogMessage(SystemOperationEnum systemOperationEnum, Long examStudentId, Long recordId,
-            MqDto mqDto) {
+                                 MqDto mqDto) {
         //mq发送消息start
         Map<String, Object> properties = new HashMap<>();
         properties.put("remark", systemOperationEnum.getCode());

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

+ 64 - 20
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamBreakHistoryServiceImpl.java

@@ -1,15 +1,23 @@
 package com.qmth.themis.business.service.impl;
 
-import org.springframework.stereotype.Service;
-
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.cache.ExamBreakCacheUtil;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.dao.TOeExamBreakHistoryMapper;
+import com.qmth.themis.business.dto.MqDto;
 import com.qmth.themis.business.entity.TOeExamBreakHistory;
+import com.qmth.themis.business.enums.ExamBreakHistoryFieldEnum;
+import com.qmth.themis.business.enums.MqTagEnum;
 import com.qmth.themis.business.enums.VerifyExceptionEnum;
+import com.qmth.themis.business.service.MqDtoService;
 import com.qmth.themis.business.service.TOeExamBreakHistoryService;
+import com.qmth.themis.business.util.MqUtil;
 import com.qmth.themis.common.exception.BusinessException;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @Description: 考试断点记录 服务实现类
@@ -20,22 +28,58 @@ import com.qmth.themis.common.exception.BusinessException;
  */
 @Service
 public class TOeExamBreakHistoryServiceImpl extends ServiceImpl<TOeExamBreakHistoryMapper, TOeExamBreakHistory> implements TOeExamBreakHistoryService {
-	
-	/**保存验证信息
-	 * @param recordId
-	 * @param entryAuthenticationId
-	 * @param entryAuthenticationResult
-	 */
-	@Override
-	public void saveAuthenticationResult(Long recordId,Long entryAuthenticationId,VerifyExceptionEnum entryAuthenticationResult) {
-		Long examBreakId=ExamRecordCacheUtil.getLastBreakId(recordId);
-		if(examBreakId==null) {
-			throw new BusinessException("未找到断点ID");
-		}
-		if(ExamBreakCacheUtil.getExamRecordId(examBreakId)==null) {
-			throw new BusinessException("未找到断点信息");
-		}
-		ExamBreakCacheUtil.setEntryAuthenticationId(examBreakId, entryAuthenticationId); 
-		ExamBreakCacheUtil.setEntryAuthenticationResult(examBreakId, entryAuthenticationResult);
-	}
+
+    @Resource
+    MqUtil mqUtil;
+
+    @Resource
+    MqDtoService mqDtoService;
+
+    /**
+     * 保存验证信息
+     *
+     * @param recordId
+     * @param entryAuthenticationId
+     * @param entryAuthenticationResult
+     */
+    @Override
+    public void saveAuthenticationResult(Long recordId, Long entryAuthenticationId, VerifyExceptionEnum entryAuthenticationResult) {
+        Long examBreakId = ExamRecordCacheUtil.getLastBreakId(recordId);
+        if (examBreakId == null) {
+            throw new BusinessException("未找到断点ID");
+        }
+        if (ExamBreakCacheUtil.getExamRecordId(examBreakId) == null) {
+            throw new BusinessException("未找到断点信息");
+        }
+        ExamBreakCacheUtil.setEntryAuthenticationId(examBreakId, entryAuthenticationId, false);
+        ExamBreakCacheUtil.setEntryAuthenticationResult(examBreakId, entryAuthenticationResult, false);
+        String[] columns = new String[]{ExamBreakHistoryFieldEnum.entry_authentication_id.name(),
+                ExamBreakHistoryFieldEnum.entry_authentication_result.name()};
+        Object[] values = new Object[]{entryAuthenticationId, entryAuthenticationResult.name()};
+        this.dataUpdatesMq(examBreakId, columns, values);
+    }
+
+    @Override
+    public void dataUpdateMq(Long breakId, String colName, Object colValue) {
+        Map<String, Object> transMap = new HashMap<String, Object>();
+        transMap.put("id", breakId);
+        transMap.put("colName", colName);
+        transMap.put("colValue", colValue);
+        //mq发送消息start
+        MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_BREAK_HISTORY_UPDATE.name(), transMap,
+                MqTagEnum.EXAM_BREAK_HISTORY_UPDATE, breakId.toString(), colName);
+        mqDtoService.assembleSendOneWayMsg(mqDto);
+    }
+
+    @Override
+    public void dataUpdatesMq(Long breakId, String[] colNames, Object[] colValues) {
+        Map<String, Object> transMap = new HashMap<String, Object>();
+        transMap.put("id", breakId);
+        transMap.put("colNames", colNames);
+        transMap.put("colValues", colValues);
+        //mq发送消息start
+        MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_BREAK_HISTORY_UPDATE_COLUMNS.name(),
+                transMap, MqTagEnum.EXAM_BREAK_HISTORY_UPDATE_COLUMNS, breakId.toString(), breakId.toString());
+        mqDtoService.assembleSendOneWayMsg(mqDto);
+    }
 }

+ 171 - 159
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -1,20 +1,22 @@
 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;
 import com.qmth.themis.business.bean.admin.*;
 import com.qmth.themis.business.cache.ExamActivityRecordCacheUtil;
+import com.qmth.themis.business.cache.ExamBreakCacheUtil;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.cache.RedisKeyHelper;
 import com.qmth.themis.business.cache.bean.*;
-import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
 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;
         //客观分最高
@@ -1014,158 +1019,165 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
      */
     @Override
     @Transactional
-    public void setExamBreak(Long recordId) {
-        if (redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_BREAK_PREFIX + recordId,
-                SystemConstant.REDIS_LOCK_EXAM_BREAK_TIME_OUT)) {
-            try {
-                Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ?
-                        0 :
-                        ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
-                alreadyBreakCount++;
-                Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
-                Long breakId = uidUtil.getId();
-                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(mqUtil.getMqGroupDomain().getTopic(), 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
-
-                Long examId = ExamRecordCacheUtil.getExamId(recordId);
-                ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
-                Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ?
-                        0 :
-                        ec.getBreakExpireSeconds();
-                if (breakExpireSeconds.intValue() > 0) {
-                    List<String> list = SystemConstant.mqDelayLevelList.subList(5, 15);
-                    String level = null;
-                    if (breakExpireSeconds.intValue() <= 60) {
-                        level = "1m";
-                    } else {
-                        Integer time = breakExpireSeconds.intValue() / 60;
-                        if (time.intValue() >= 30) {
-                            level = "30m";
-                        } else {
-                            for (String s : list) {
-                                Integer value = Integer.parseInt(s.substring(0, s.length() - 1));
-                                if (time.intValue() <= value.intValue()) {
-                                    level = value + "m";
-                                    break;
+    public Boolean setExamBreak(Long recordId) {
+        boolean lock = false;
+        for (int i = 0; i < SystemConstant.MAX_EXAM_STATUS_COUNT; i++) {
+            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 :
+                            ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
+                    alreadyBreakCount++;
+                    Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
+                    Long breakId = uidUtil.getId();
+                    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);
+                    String[] columns = new String[]{ExamRecordFieldEnum.last_break_id.name(),
+                            ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_break_time.name(),
+                            ExamRecordFieldEnum.already_break_count.name()};
+                    Object[] values = new Object[]{breakId, ExamRecordStatusEnum.BREAK_OFF, lastBreakTimeNow,
+                            alreadyBreakCount};
+                    ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                    if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum == ExamRecordStatusEnum.BREAK_OFF) {
+                        this.dataUpdatesMq(recordId, columns, values);
+                        //考试断点异常原因 发送mq start
+                        MqDto mqDtoBreak = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_BREAK.name(),
+                                ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId),
+                                String.valueOf(recordId));
+                        mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
+                        //考试断点异常原因 发送mq end
+
+                        Long examId = ExamRecordCacheUtil.getExamId(recordId);
+                        ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
+                        Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ?
+                                0 :
+                                ec.getBreakExpireSeconds();
+                        if (breakExpireSeconds.intValue() > 0) {
+                            List<String> list = SystemConstant.mqDelayLevelList.subList(5, 15);
+                            String level = null;
+                            if (breakExpireSeconds.intValue() <= 60) {
+                                level = "1m";
+                            } else {
+                                Integer time = breakExpireSeconds.intValue() / 60;
+                                if (time.intValue() >= 30) {
+                                    level = "30m";
+                                } else {
+                                    for (String s : list) {
+                                        Integer value = Integer.parseInt(s.substring(0, s.length() - 1));
+                                        if (time.intValue() <= value.intValue()) {
+                                            level = value + "m";
+                                            break;
+                                        }
+                                    }
                                 }
                             }
+                            Map<String, Object> tranMap = new HashMap<>();
+                            Integer time = SystemConstant.mqDelayLevel.get(level);
+                            LocalDateTime dt = LocalDateTime.now();
+                            if (level.contains("m")) {
+                                dt = dt.plusMinutes(Long.parseLong(level.replace("m", "")));
+                            } else {
+                                dt = dt.plusSeconds(Long.parseLong(level.replace("s", "")));
+                            }
+                            tranMap.put("recordId", recordId);
+                            tranMap.put("timeOut", time);
+                            tranMap.put("mqExecTime", dt.toInstant(ZoneOffset.of("+8")).toEpochMilli());
+                            //考试断点延时消息 发送mq start
+                            MqDto mqDtoBreakDelay = new MqDto(mqUtil.getMqGroupDomain().getTopic(),
+                                    MqTagEnum.EXAM_BREAK_DELAY.name(), MqTagEnum.EXAM_BREAK_DELAY, MqTagEnum.EXAM_BREAK_DELAY,
+                                    String.valueOf(recordId), tranMap, String.valueOf(recordId));
+                            mqDtoService.assembleSendAsyncDelayMsg(mqDtoBreakDelay);
+                            //考试断点延时消息 发送mq end
                         }
+                        //更新场次-考试记录缓存
+                        ExamActivityRecordCacheUtil.setExamRecordStatus(examActivityId, recordId,
+                                new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId),
+                                        ExamRecordCacheUtil.getStatus(recordId)));
                     }
-                    Map<String, Object> tranMap = new HashMap<>();
-                    Integer time = SystemConstant.mqDelayLevel.get(level);
-                    LocalDateTime dt = LocalDateTime.now();
-                    if (level.contains("m")) {
-                        dt = dt.plusMinutes(Long.parseLong(level.replace("m", "")));
+                    break;
+                } catch (Exception e) {
+                    log.error("请求出错", e);
+                    if (e instanceof BusinessException) {
+                        throw new BusinessException(e.getMessage());
                     } else {
-                        dt = dt.plusSeconds(Long.parseLong(level.replace("s", "")));
+                        throw new RuntimeException(e);
+                    }
+                } finally {
+                    if (Objects.nonNull(recordId)) {
+                        redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
                     }
-                    tranMap.put("recordId", recordId);
-                    tranMap.put("timeOut", time);
-                    tranMap.put("mqExecTime", dt.toInstant(ZoneOffset.of("+8")).toEpochMilli());
-                    //考试断点延时消息 发送mq start
-                    MqDto mqDtoBreakDelay = new MqDto(mqUtil.getMqGroupDomain().getTopic(),
-                            MqTagEnum.EXAM_BREAK_DELAY.name(), MqTagEnum.EXAM_BREAK_DELAY, MqTagEnum.EXAM_BREAK_DELAY,
-                            String.valueOf(recordId), tranMap, String.valueOf(recordId));
-                    mqDtoService.assembleSendAsyncDelayMsg(mqDtoBreakDelay);
-                    //考试断点延时消息 发送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);
+            } else {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
                 }
             }
         }
+        if (!lock) {
+            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 lock;
     }
 
     /**
      * 发送断点信息
      *
      * @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);
+            }
+            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;
     }

+ 0 - 10
themis-business/src/main/resources/mapper/TEExamReexamMapper.xml

@@ -52,7 +52,6 @@
                 <if test="identity != null and identity !=''">
                     and tees.identity like CONCAT('%', #{identity},'%')
                 </if>
-                and tees.enable = 1
                 and tee.monitor_status <![CDATA[ <> ]]> 'FINISHED'
             </where>
         ) t where t.examCount = 0 and statusCount = 0
@@ -124,9 +123,6 @@
              <if test="applyName != null and applyName !=''">
                  and tbu.name like CONCAT('%', #{applyName},'%')
              </if>
-             and tee.enable = 1
-             and teea.enable = 1
-             and tees.enable = 1
              and teer.status = 1
          </where>
          order by tees.room_code
@@ -156,9 +152,6 @@
             <if test="orgId != null and orgId != ''">
                 and tee.org_id = #{orgId}
             </if>
-            and tee.enable = 1
-            and teea.enable = 1
-            and tees.enable = 1
             and teer.status = 1
         </where>
     </select>
@@ -232,9 +225,6 @@
             <if test="applyName != null and applyName !=''">
                 and tbu.name like CONCAT('%', #{applyName},'%')
             </if>
-            and tee.enable = 1
-            and teea.enable = 1
-            and tees.enable = 1
             and (teer.status = 0 or teer.status = 2)
         </where>
         order by tees.room_code

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

+ 6 - 7
themis-business/src/main/resources/mapper/TIeInvigilateWarnInfoMapper.xml

@@ -7,16 +7,15 @@
         from t_ie_invigilate_warn_info tiiwi
         left join t_e_exam_student tees on tees.id = tiiwi.exam_student_id
         left join t_e_exam tee on tee.id = tees.exam_id
-        inner join (select toer.id from t_oe_exam_record toer where EXISTS(select tees.id from t_e_exam_student tees where EXISTS (select distinct tbeiu.room_code from t_b_exam_invigilate_user tbeiu
-        where
-        <if test="userId != null and userId != ''">
-            tbeiu.user_id = #{userId} and
-        </if>
-        tbeiu.room_code = tees.room_code and tees.exam_id = tbeiu.exam_id and toer.exam_student_id = tees.id))) t on t.id = tiiwi.exam_record_id
-        <where>
+        <where> 1 = 1
             <if test="examId != null and examId != ''">
                 and tiiwi.exam_id = #{examId}
             </if>
+            <if test="userId != null and userId != ''">
+                and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+                where tbeiu.user_id = #{userId}
+                and tbeiu.exam_id = tees.exam_id and tbeiu.room_code = tees.room_code)
+            </if>
             <if test="orgId != null and orgId != ''">
                 and tee.org_id = #{orgId}
             </if>

+ 42 - 113
themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml

@@ -110,15 +110,6 @@
 		left join t_e_exam_student s on t.exam_student_id = s.id
 		left join t_e_exam tee on tee.id = t.exam_id
 		left join t_e_exam_activity teea on teea.id = t.exam_activity_id
-		inner join (select toer.id from t_oe_exam_record toer where EXISTS(select
-		tees.id from t_e_exam_student tees where EXISTS (select distinct
-		tbeiu.room_code from t_b_exam_invigilate_user tbeiu
-		where
-		<if test="userId != null and userId != ''">
-			tbeiu.user_id = #{userId} and
-		</if>
-		tbeiu.room_code = tees.room_code and tees.exam_id = tbeiu.exam_id and toer.exam_student_id = tees.id)))
-		t1 on t1.id = t.id
 	</sql>
 
 	<sql id="invigilatePageFoot">
@@ -126,6 +117,11 @@
 			<if test="examId != null and examId != ''">
 				and t.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = s.exam_id and tbeiu.room_code = s.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -160,18 +156,20 @@
 			<if test="clientWebsocketStatus != null and clientWebsocketStatus != ''">
 				and t.client_websocket_status = #{clientWebsocketStatus}
 			</if>
-			and s.enable = 1
-			and tee.enable = 1
-			and teea.enable = 1
 			and teea.finish_time > unix_timestamp(current_timestamp()) * 1000
 		</where>
 	</sql>
 
 	<sql id="invigilatePageHistoryFoot">
-		<where>
+		<where> 1 = 1
 			<if test="examId != null and examId != ''">
 				and t.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = t.exam_id and tbeiu.room_code = s.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -203,9 +201,6 @@
 			<if test="minWarningCount != null and minWarningCount != '' or minWarningCount == 0">
 				and t.warning_count &gt;= #{minWarningCount}
 			</if>
-			and s.enable = 1
-			and tee.enable = 1
-			and teea.enable = 1
 		</where>
 	</sql>
 
@@ -218,11 +213,6 @@
 		<include refid="invigilatePageHead"/>
 		,(select count(1) from t_ie_invigilate_warn_info tiiwi where tiiwi.exam_record_id = t.id and
 		tiiwi.approve_status = 0) as warningNew
-		<!--,case
-		when tee.mode = 'ANYTIME' then SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		when tee.mode = 'TOGETHER' and tee.force_finish = 1 then SEC_TO_TIME((IFNULL(teea.finish_time, tee.end_time) - unix_timestamp(current_timestamp()) * 1000) / 1000)
-		else SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		end as remainTime-->
 		<include refid="invigilatePageMiddle"/>
 		<include refid="invigilatePageFoot"/>
 		<if test="paperDownload != null and paperDownload != '' or paperDownload == 0">
@@ -245,11 +235,6 @@
 		<include refid="invigilatePageHead" />
 		,t.monitor_live_url as monitorLiveUrl
 		,(select count(1) from t_ie_invigilate_warn_info tiiwi where tiiwi.exam_record_id = t.id and tiiwi.approve_status = 0) as warningNew
-		<!--,case
-		when tee.mode = 'ANYTIME' then SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		when tee.mode = 'TOGETHER' and tee.force_finish = 1 then SEC_TO_TIME((IFNULL(teea.finish_time, tee.end_time) - unix_timestamp(current_timestamp()) * 1000) / 1000)
-		else SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		end as remainTime-->
 		<include refid="invigilatePageMiddle" />
 		<include refid="invigilatePageFoot" />
 		<if test="paperDownload != null and paperDownload != '' or paperDownload == 0">
@@ -267,11 +252,6 @@
 		<include refid="invigilatePageHead" />
 		,t.monitor_live_url as monitorLiveUrl
 		,(select count(1) from t_ie_invigilate_warn_info tiiwi where tiiwi.exam_record_id = t.id and tiiwi.approve_status = 0) as warningNew
-		<!--,case
-		when tee.mode = 'ANYTIME' then SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		when tee.mode = 'TOGETHER' and tee.force_finish = 1 then SEC_TO_TIME((IFNULL(teea.finish_time, tee.end_time) - unix_timestamp(current_timestamp()) * 1000) / 1000)
-		else SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		end as remainTime-->
 		<include refid="invigilatePageMiddle" />
 		<where> 1 = 1
 			<if test="examId != null and examId != ''">
@@ -290,11 +270,6 @@
 		,(select count(1) from t_ie_invigilate_exception_info tiiei where tiiei.exam_record_id = t.id) as exceptionCount
 		,(select count(1) from t_ie_invigilate_warn_info tiiwi where tiiwi.exam_record_id = t.id and tiiwi.`type` =
 		'FACE_COUNT_ERROR' and tiiwi.`level` = 'D8') as multipleFaceCount
-		<!--,case
-		when tee.mode = 'ANYTIME' then SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		when tee.mode = 'TOGETHER' and tee.force_finish = 1 then SEC_TO_TIME((IFNULL(teea.finish_time, tee.end_time) - unix_timestamp(current_timestamp()) * 1000) / 1000)
-		else SEC_TO_TIME(IFNULL(teea.max_duration_seconds, tee.max_duration_seconds) - t.duration_seconds)
-		end as remainTime-->
 		<include refid="invigilatePageMiddle" />
 		<include refid="invigilatePageFoot" />
 		<if test="status == null or status == ''">
@@ -346,21 +321,17 @@
 		from t_ie_invigilate_warn_info tiiwi
 		left join t_e_exam tee on tee.id = tiiwi.exam_id
 		left join t_e_exam_activity teea on teea.id = tiiwi.exam_activity_id
-		inner join (select toer.id from t_oe_exam_record toer where EXISTS(select
-		tees.id from t_e_exam_student tees where EXISTS (select distinct
-		tbeiu.room_code from t_b_exam_invigilate_user tbeiu
-		where
-		<if test="userId != null and userId != ''">
-			tbeiu.user_id = #{userId} and
-		</if>
-		tbeiu.room_code = tees.room_code and tees.exam_id = tbeiu.exam_id and toer.exam_student_id = tees.id)))
-		t on t.id = tiiwi.exam_record_id
-		left join t_oe_exam_record toer on toer.id = t.id
+		left join t_oe_exam_record toer on toer.id = tiiwi.exam_record_id
 		left join t_e_exam_student tees on tees.id = tiiwi.exam_student_id
-		<where>
+		<where> 1 = 1
 			<if test="examId != null and examId != ''">
 				and tiiwi.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = tees.exam_id and tbeiu.room_code = tees.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -385,8 +356,6 @@
 			<if test="minWarningCount != null and minWarningCount != '' or minWarningCount == 0">
 				and toer.warning_count &gt;= #{minWarningCount}
 			</if>
-			and tee.enable = 1
-			and teea.enable = 1
 		</where>
 		) t,(SELECT @i := 0) as i
 		<where>
@@ -436,21 +405,17 @@
 		from t_ie_invigilate_warn_info tiiwi
 		left join t_e_exam tee on tee.id = tiiwi.exam_id
 		left join t_e_exam_activity teea on teea.id = tiiwi.exam_activity_id
-		inner join (select toer.id from t_oe_exam_record toer where EXISTS(select
-		tees.id from t_e_exam_student tees where EXISTS (select distinct
-		tbeiu.room_code from t_b_exam_invigilate_user tbeiu
-		where
-		<if test="userId != null and userId != ''">
-			tbeiu.user_id = #{userId} and
-		</if>
-		tbeiu.room_code = tees.room_code and tees.exam_id = tbeiu.exam_id and toer.exam_student_id = tees.id)))
-		t on t.id = tiiwi.exam_record_id
 		left join t_oe_exam_record toer on toer.id = t.id
 		left join t_e_exam_student tees on tees.id = tiiwi.exam_student_id
-		<where>
+		<where> 1 = 1
 			<if test="examId != null and examId != ''">
 				and tiiwi.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = tees.exam_id and tbeiu.room_code = tees.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -475,8 +440,6 @@
 			<if test="minWarningCount != null and minWarningCount != '' or minWarningCount == 0">
 				and toer.warning_count &gt;= #{minWarningCount}
 			</if>
-			and tee.enable = 1
-			and teea.enable = 1
 		</where>
 		) t
 		<where> 1 = 1
@@ -520,20 +483,15 @@
 		left join t_e_exam_activity teea on
 		teea.id = tees.exam_activity_id
 		left join t_e_student tes on tees.student_id = tes.id
-		<where>
-			exists(
-			select
-			distinct tbeiu.exam_id
-			from
-			t_b_exam_invigilate_user tbeiu where 1 = 1
-			<if test="userId != null and userId != ''">
-				and tbeiu.user_id = #{userId}
-			</if>
-			and tbeiu.exam_id = tees.exam_id
-			)
+		<where> 1 = 1
 			<if test="examId != null and examId != ''">
 				and tees.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = tees.exam_id and tbeiu.room_code = tees.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -552,8 +510,6 @@
 			<if test="identity != null and identity !=''">
 				and tees.identity like CONCAT('%', #{identity},'%')
 			</if>
-			and tee.enable = 1
-			and teea.enable = 1
 			and tee.monitor_status <![CDATA[ <> ]]> 'FINISHED'
 		</where>
 		order by tees.room_code
@@ -582,20 +538,15 @@
 		left join t_e_exam_activity teea on
 		teea.id = tees.exam_activity_id
 		left join t_e_student tes on tees.student_id = tes.id
-		<where>
-			exists(
-			select
-			distinct tbeiu.exam_id
-			from
-			t_b_exam_invigilate_user tbeiu where 1 = 1
-			<if test="userId != null and userId != ''">
-				and tbeiu.user_id = #{userId}
-			</if>
-			    and tbeiu.exam_id = tees.exam_id
-			)
+		<where> 1 = 1
 			<if test="examId != null and examId != ''">
 				and tees.exam_id = #{examId}
 			</if>
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = tees.exam_id and tbeiu.room_code = tees.room_code)
+			</if>
 			<if test="orgId != null and orgId != ''">
 				and tee.org_id = #{orgId}
 			</if>
@@ -614,8 +565,6 @@
 			<if test="identity != null and identity !=''">
 				and tees.identity like CONCAT('%', #{identity},'%')
 			</if>
-			and tee.enable = 1
-			and teea.enable = 1
 			and tee.monitor_status <![CDATA[ <> ]]> 'FINISHED'
 		</where>
 		group by tee.id,tees.room_code,tee.name,teea.id,teea.code,tees.`identity`,tees.name,leftExamCount,status,tes.mobile_number
@@ -751,35 +700,15 @@
 		tee.id = t.exam_id
 		left join t_e_exam_activity teea on
 		teea.id = t.exam_activity_id
-		inner join (
-		select
-		toer.id
-		from
-		t_oe_exam_record toer
-		where
-		EXISTS(
-		select
-		tees.id
-		from
-		t_e_exam_student tees
-		where
-		EXISTS (
-		select
-		distinct tbeiu.exam_id
-		from
-		t_b_exam_invigilate_user tbeiu where 1 = 1
-		<if test="userId != null and userId != ''">
-			and tbeiu.user_id = #{userId}
-		</if>
-		and tbeiu.exam_id = tees.exam_id and tees.room_code = tbeiu.room_code))) t1 on
-		t1.id = t.id
-		<where> 1 = 1
+		<where>
 			<if test="examId != null and examId != ''">
 				and t.exam_id = #{examId}
 			</if>
-			and s.enable = 1
-			and tee.enable = 1
-			and teea.enable = 1
+			<if test="userId != null and userId != ''">
+				and exists (select tbeiu.exam_id from t_b_exam_invigilate_user tbeiu
+				where tbeiu.user_id = #{userId}
+				and tbeiu.exam_id = t.exam_id and tbeiu.room_code = s.room_code)
+			</if>
 			and (t.status <![CDATA[ <> ]]> 'PERSISTED' and t.status <![CDATA[ <> ]]> 'FINISHED')
 		</where> ) t
 		group by

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

@@ -192,6 +192,10 @@ public enum ExceptionResultEnum {
 
     AUTHORIZATION_INVALID(401, 401008, "authorization无效"),
 
+    SOURCE_INVALID(401, 401009, "source无效"),
+
+    RECORD_ID_INVALID(401, 401009, "recordId无效"),
+
     /**
      * 404
      */
@@ -237,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;
     private int code;

+ 10 - 2
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -15,6 +15,7 @@ import com.qmth.themis.business.entity.TEExam;
 import com.qmth.themis.business.enums.*;
 import com.qmth.themis.business.service.MqDtoService;
 import com.qmth.themis.business.service.TEExamService;
+import com.qmth.themis.business.service.TOeExamBreakHistoryService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.util.JacksonUtil;
 import com.qmth.themis.business.util.MqUtil;
@@ -51,6 +52,9 @@ public class TEExamController {
     @Resource
     TOeExamRecordService tOeExamRecordService;
 
+    @Resource
+    TOeExamBreakHistoryService tOeExamBreakHistoryService;
+
     @Resource
     MqUtil mqUtil;
 
@@ -159,8 +163,12 @@ public class TEExamController {
                     JSONObject jsonObject = JSONObject.parseObject(param.getReason());
                     ExceptionEnum exceptionEnum = ExceptionEnum.valueOf(ExceptionEnum.convertToName(String.valueOf(jsonObject.get("type"))));
                     String reason = String.valueOf(jsonObject.get("reason"));
-                    ExamBreakCacheUtil.setBreakReason(breakId, exceptionEnum);
-                    ExamBreakCacheUtil.setResumeReason(breakId, reason);
+                    ExamBreakCacheUtil.setBreakReason(breakId, exceptionEnum, false);
+                    ExamBreakCacheUtil.setResumeReason(breakId, reason, false);
+                    String[] columns = new String[]{ExamBreakHistoryFieldEnum.break_reason.name(),
+                            ExamBreakHistoryFieldEnum.resume_reason.name()};
+                    Object[] values = new Object[]{exceptionEnum.name(), reason};
+                    tOeExamBreakHistoryService.dataUpdatesMq(breakId, columns, values);
                     //考试断点异常原因 发送mq start
                     MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXCEPTION_LOG.name(), JacksonUtil.parseJson(param), MqTagEnum.EXCEPTION_LOG, String.valueOf(param.getRecordId()), param.getReason());
                     mqDtoService.assembleSendOneWayMsg(mqDto);

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

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

+ 3 - 3
themis-exam/src/main/java/com/qmth/themis/exam/config/ExamConstant.java

@@ -33,7 +33,7 @@ public class ExamConstant {
         //客户端考试结束
         if (clientStop) {
             ConcurrentHashMap<String, WebSocketOeServer> webSocketOeMap = WebSocketOeServer.getWebSocketMap();
-            if (Objects.nonNull(webSocketOeMap.get(clientWebsocketId))) {
+            if (Objects.nonNull(clientWebsocketId) && Objects.nonNull(webSocketOeMap.get(clientWebsocketId))) {
                 WebSocketOeServer webSocketOeServer = webSocketOeMap.get(clientWebsocketId);
                 if (Objects.nonNull(webSocketOeServer.getRecordId()) && webSocketOeServer.getRecordId().longValue() == recordId.longValue()) {
                     Map map = new HashMap<>();
@@ -46,7 +46,7 @@ public class ExamConstant {
 
         //移动端考试结束
         ConcurrentHashMap<String, WebSocketMobileServer> webSocketMap = WebSocketMobileServer.getWebSocketMap();
-        if (Objects.nonNull(webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name()))) {
+        if (Objects.nonNull(clientWebsocketId) && Objects.nonNull(webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name()))) {
             WebSocketMobileServer webSocketMobileServer = webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name());
             if (Objects.nonNull(webSocketMobileServer.getRecordId()) && webSocketMobileServer.getRecordId().longValue() == recordId.longValue()) {
                 Map map = new HashMap<>();
@@ -55,7 +55,7 @@ public class ExamConstant {
                 webSocketMobileServer.sendMessage(websocketDto);
             }
         }
-        if (Objects.nonNull(webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name()))) {
+        if (Objects.nonNull(clientWebsocketId) && Objects.nonNull(webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name()))) {
             WebSocketMobileServer webSocketMobileServer = webSocketMap.get(clientWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name());
             if (Objects.nonNull(webSocketMobileServer.getRecordId()) && webSocketMobileServer.getRecordId().longValue() == recordId.longValue()) {
                 Map map = new HashMap<>();

+ 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);

+ 26 - 6
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
@@ -628,13 +626,13 @@ public class MqLogicServiceImpl implements MqLogicService {
     public void execMqRecordUpdateLogic(MqDto mqDto, String key) {
         Gson gson = new Gson();
         String tag = mqDto.getTag();
-        if (Objects.equals(tag, MqTagEnum.EXAM_RECORD_UPDATE.name())) {//单字段更新
+        if (Objects.equals(tag, MqTagEnum.EXAM_RECORD_UPDATE.name())) {//考试记录单字段更新
             Map<String, Object> param = (Map<String, Object>) mqDto.getBody();
             Long recordId = Long.parseLong(String.valueOf(param.get("recordId")));
             String colName = (String) param.get("colName");
             Object colValue = param.get("colValue");
             examRecordService.dataUpdate(recordId, colName, colValue);
-        } else if (Objects.equals(tag, MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS.name())) {//多字段更新
+        } else if (Objects.equals(tag, MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS.name())) {//考试记录多字段更新
             Map<String, Object> param = (Map<String, Object>) mqDto.getBody();
             Long recordId = Long.parseLong(String.valueOf(param.get("recordId")));
             List<String> colNames = (List<String>) (param.get("colNames"));
@@ -647,6 +645,28 @@ public class MqLogicServiceImpl implements MqLogicService {
             }
             tOeExamRecordUpdateWrapper.lambda().eq(TOeExamRecord::getId, recordId);
             examRecordService.update(tOeExamRecordUpdateWrapper);
+        } else if (Objects.equals(tag, MqTagEnum.EXAM_BREAK_HISTORY_UPDATE.name())) {//断点记录多字段更新
+            Map<String, Object> param = (Map<String, Object>) mqDto.getBody();
+            Long breakId = Long.parseLong(String.valueOf(param.get("id")));
+            String colName = (String) param.get("colName");
+            Object colValue = param.get("colValue");
+            UpdateWrapper<TOeExamBreakHistory> tOeExamBreakHistoryUpdateWrapper = new UpdateWrapper<>();
+            tOeExamBreakHistoryUpdateWrapper.set(colName, colValue);
+            tOeExamBreakHistoryUpdateWrapper.lambda().eq(TOeExamBreakHistory::getId, breakId);
+            tOeExamBreakHistoryService.update(tOeExamBreakHistoryUpdateWrapper);
+        } else if (Objects.equals(tag, MqTagEnum.EXAM_BREAK_HISTORY_UPDATE_COLUMNS.name())) {//断点记录多字段更新
+            Map<String, Object> param = (Map<String, Object>) mqDto.getBody();
+            Long breakId = Long.parseLong(String.valueOf(param.get("id")));
+            List<String> colNames = (List<String>) (param.get("colNames"));
+            List<Object> colValues = (List<Object>) param.get("colValues");
+            UpdateWrapper<TOeExamBreakHistory> tOeExamBreakHistoryUpdateWrapper = new UpdateWrapper<>();
+            for (int i = 0; i < colNames.size(); i++) {
+                String colName = colNames.get(i);
+                Object colValue = colValues.get(i);
+                tOeExamBreakHistoryUpdateWrapper.set(colName, colValue);
+            }
+            tOeExamBreakHistoryUpdateWrapper.lambda().eq(TOeExamBreakHistory::getId, breakId);
+            tOeExamBreakHistoryService.update(tOeExamBreakHistoryUpdateWrapper);
         }
         mqDto.setAck(SystemConstant.STANDARD_ACK_TYPE);
         TMRocketMessage tmRocketMessage = gson.fromJson(gson.toJson(mqDto), TMRocketMessage.class);

+ 1 - 1
themis-task/src/main/java/com/qmth/themis/task/start/StartRunning.java

@@ -101,7 +101,7 @@ public class StartRunning implements CommandLineRunner {
         rocketMessageConsumer.setRocketMQConsumer(nameServer, dictionaryConfig.mqConfigDomain().getMap().get(MqGroupEnum.EXAM_RECORD_PERSISTED_GROUP.name()), dictionaryConfig.mqConfigDomain().getTopic(), MqTagEnum.EXAM_RECORD_PERSISTED.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(ExamRecordPersistedConcurrentlyImpl.class));
 
         //考试记录数据更新
-        rocketMessageConsumer.setRocketMQConsumer(nameServer, dictionaryConfig.mqConfigDomain().getMap().get(MqGroupEnum.EXAM_RECORD_UPDATE_GROUP.name()), dictionaryConfig.mqConfigDomain().getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name() + "||" + MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(ExamRecordUpdateConcurrentlyImpl.class));
+        rocketMessageConsumer.setRocketMQConsumer(nameServer, dictionaryConfig.mqConfigDomain().getMap().get(MqGroupEnum.EXAM_RECORD_UPDATE_GROUP.name()), dictionaryConfig.mqConfigDomain().getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name() + "||" + MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS.name() + "||" + MqTagEnum.EXAM_BREAK_HISTORY_UPDATE.name() + "||" + MqTagEnum.EXAM_BREAK_HISTORY_UPDATE_COLUMNS.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(ExamRecordUpdateConcurrentlyImpl.class));
         //考试记录数据初始化
         rocketMessageConsumer.setRocketMQConsumer(nameServer, dictionaryConfig.mqConfigDomain().getMap().get(MqGroupEnum.EXAM_RECORD_INIT_GROUP.name()), dictionaryConfig.mqConfigDomain().getTopic(), MqTagEnum.EXAM_RECORD_INIT.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(ExamRecordInitConcurrentlyImpl.class));