Browse Source

Merge branch 'dev'
123

wangliang 4 years ago
parent
commit
23be2a756e
25 changed files with 980 additions and 435 deletions
  1. 11 2
      themis-backend/src/main/java/com/qmth/themis/backend/api/TIeInvigilateCallMobileController.java
  2. 13 5
      themis-backend/src/main/java/com/qmth/themis/backend/api/TIeInvigilateController.java
  3. 21 0
      themis-backend/src/main/java/com/qmth/themis/backend/api/TIeReportController.java
  4. 71 0
      themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorCountBean.java
  5. 25 0
      themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorHourWarnCountBean.java
  6. 28 0
      themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorWarnDistributionBean.java
  7. 13 5
      themis-business/src/main/java/com/qmth/themis/business/bean/backend/InvigilateListDetailBean.java
  8. 2 1
      themis-business/src/main/java/com/qmth/themis/business/dao/TIeExamInvigilateCallMapper.java
  9. 29 6
      themis-business/src/main/java/com/qmth/themis/business/dao/TOeExamRecordMapper.java
  10. 38 0
      themis-business/src/main/java/com/qmth/themis/business/dto/response/TIeExamInvigilateCallDto.java
  11. 23 0
      themis-business/src/main/java/com/qmth/themis/business/entity/TEExamStudentLog.java
  12. 5 0
      themis-business/src/main/java/com/qmth/themis/business/enums/MqGroupEnum.java
  13. 1 1
      themis-business/src/main/java/com/qmth/themis/business/enums/MqTagEnum.java
  14. 2 1
      themis-business/src/main/java/com/qmth/themis/business/service/TIeExamInvigilateCallService.java
  15. 15 3
      themis-business/src/main/java/com/qmth/themis/business/service/TIeReportService.java
  16. 2 1
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TIeExamInvigilateCallServiceImpl.java
  17. 476 350
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TIeReportServiceImpl.java
  18. 1 0
      themis-business/src/main/resources/db/init.sql
  19. 11 5
      themis-business/src/main/resources/mapper/TIeExamInvigilateCallMapper.xml
  20. 97 0
      themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml
  21. 11 4
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java
  22. 19 17
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEStudentController.java
  23. 3 1
      themis-exam/src/main/java/com/qmth/themis/exam/start/StartRunning.java
  24. 1 1
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketMobileServer.java
  25. 62 32
      themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

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

@@ -8,9 +8,11 @@ import com.qmth.themis.business.annotation.ApiJsonProperty;
 import com.qmth.themis.business.base.BasePage;
 import com.qmth.themis.business.bean.mobile.MobileAuthorizationMonitorBean;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
+import com.qmth.themis.business.config.SystemConfig;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.AuthDto;
 import com.qmth.themis.business.dto.MqDto;
+import com.qmth.themis.business.dto.response.TIeExamInvigilateCallDto;
 import com.qmth.themis.business.entity.TBSession;
 import com.qmth.themis.business.entity.TBUser;
 import com.qmth.themis.business.entity.TIeExamInvigilateCall;
@@ -32,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -62,9 +65,12 @@ public class TIeInvigilateCallMobileController {
     @Resource
     TencentYunUtil tencentYunUtil;
 
+    @Resource
+    SystemConfig systemConfig;
+
     @ApiOperation(value = "监考监控通话查询接口")
     @RequestMapping(value = "/call/list", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = TIeExamInvigilateCall.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = TIeExamInvigilateCallDto.class)})
     public Result callList(@ApiParam(value = "考试批次id", required = true) @RequestParam(required = true) Long examId, @ApiParam(value = "通话状态", required = false) @RequestParam(required = false) String callStatus, @ApiParam(value = "分页页码", required = true) @RequestParam int pageNumber, @ApiParam(value = "分页数", required = true) @RequestParam int pageSize) {
         if (Objects.isNull(examId) || Objects.equals(examId, "")) {
             throw new BusinessException(ExceptionResultEnum.EXAM_ID_IS_NULL);
@@ -80,7 +86,10 @@ public class TIeInvigilateCallMobileController {
         if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
             userId = tbUser.getId();
         }
-        IPage<TIeExamInvigilateCall> tIeExamInvigilateCallIPage = tIeExamInvigilateCallService.examInvigilateCallQuery(new Page<>(pageNumber, pageSize), examId, userId, tbUser.getOrgId(), MonitorStatusSourceEnum.START.name(), callStatus);
+        IPage<TIeExamInvigilateCallDto> tIeExamInvigilateCallIPage = tIeExamInvigilateCallService.examInvigilateCallQuery(new Page<>(pageNumber, pageSize), examId, userId, tbUser.getOrgId(), MonitorStatusSourceEnum.START.name(), callStatus);
+        tIeExamInvigilateCallIPage.getRecords().forEach(s -> {
+            s.setBasePhotoPath(systemConfig.getProperty("aliyun.oss.url") + File.separator + s.getBasePhotoPath());
+        });
         BasePage basePage = new BasePage(tIeExamInvigilateCallIPage.getRecords(), tIeExamInvigilateCallIPage.getCurrent(), tIeExamInvigilateCallIPage.getSize(), tIeExamInvigilateCallIPage.getTotal());
         return ResultUtil.ok(basePage);
     }

+ 13 - 5
themis-backend/src/main/java/com/qmth/themis/backend/api/TIeInvigilateController.java

@@ -3,8 +3,6 @@ package com.qmth.themis.backend.api;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.google.common.reflect.TypeToken;
-import com.google.gson.Gson;
 import com.qmth.themis.business.annotation.ApiJsonObject;
 import com.qmth.themis.business.annotation.ApiJsonProperty;
 import com.qmth.themis.business.base.BasePage;
@@ -14,6 +12,7 @@ import com.qmth.themis.business.cache.RedisKeyHelper;
 import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
 import com.qmth.themis.business.cache.bean.ExamCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
+import com.qmth.themis.business.config.SystemConfig;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.AuthDto;
 import com.qmth.themis.business.dto.ExamPropCountDto;
@@ -34,6 +33,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.io.File;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -87,6 +87,12 @@ public class TIeInvigilateController {
     @Resource
     TEExamService teExamService;
 
+    @Resource
+    TEStudentService teStudentService;
+
+    @Resource
+    SystemConfig systemConfig;
+
     @ApiOperation(value = "实时监控台列表接口")
     @RequestMapping(value = "/list", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = InvigilateListBean.class)})
@@ -191,6 +197,8 @@ public class TIeInvigilateController {
         ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(examRecordId);
         Integer breachStatus = Objects.isNull(ExamRecordCacheUtil.getBreachStatus(examRecordId)) ? null : ExamRecordCacheUtil.getBreachStatus(examRecordId);
         ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
+        TEStudent teStudent = teStudentService.getById(examStudentCacheBean.getStudentId());
+        String basePhotoPath = systemConfig.getProperty("aliyun.oss.url") + File.separator + teStudent.getBasePhotoPath();
         String identity = examStudentCacheBean.getIdentity();
         String examStudentName = examStudentCacheBean.getName();
         String courseNameCode = examStudentCacheBean.getCourseName() + "(" + examStudentCacheBean.getCourseCode() + ")";
@@ -198,12 +206,12 @@ public class TIeInvigilateController {
         String roomName = examStudentCacheBean.getRoomName();
         ExamActivityCacheBean examActivityCacheBean = teExamActivityService.getExamActivityCacheBean(examActivityId);
         ExamCacheBean examCacheBean = teExamService.getExamCacheBean(examId);
-        InvigilateListDetailBean invigilateListDetailBean = new InvigilateListDetailBean(examCacheBean.getName(), examActivityCacheBean.getCode(), examId, examActivityId, examStudentId, examRecordId, identity, examStudentName, courseNameCode, status, roomCode, roomName, breachStatus);
+        InvigilateListDetailBean invigilateListDetailBean = new InvigilateListDetailBean(examCacheBean.getName(), examActivityCacheBean.getCode(), examId, examActivityId, examStudentId, examRecordId, identity, examStudentName, courseNameCode, status, roomCode, roomName, breachStatus, basePhotoPath);
 
         //考生轨迹
         QueryWrapper<TEExamStudentLog> teExamStudentLogQueryWrapper = new QueryWrapper<>();
-        teExamStudentLogQueryWrapper.lambda().eq(TEExamStudentLog::getExamRecordId,examRecordId)
-                .eq(TEExamStudentLog::getExamStudentId,examStudentId);
+        teExamStudentLogQueryWrapper.lambda().eq(TEExamStudentLog::getExamRecordId, examRecordId)
+                .eq(TEExamStudentLog::getExamStudentId, examStudentId);
         List<TEExamStudentLog> teExamStudentLogList = teExamStudentLogService.list(teExamStudentLogQueryWrapper);
         invigilateListDetailBean.setExamStudentLogList(teExamStudentLogList);
 

+ 21 - 0
themis-backend/src/main/java/com/qmth/themis/backend/api/TIeReportController.java

@@ -202,4 +202,25 @@ public class TIeReportController {
         }
         return ResultUtil.ok(reportService.examInvigilateReport(examId, userId));
     }
+    
+    @ApiOperation(value = "考情监控-人数")
+    @RequestMapping(value = "/examination_monitor/count", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "结果信息")})
+    public Result examinationMonitorCount() {
+        return ResultUtil.ok(reportService.examinationMonitorCount());
+    }
+    
+    @ApiOperation(value = "考情监控-预警分布")
+    @RequestMapping(value = "/examination_monitor/warn_distribution", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "结果信息")})
+    public Result warnDistribution() {
+        return ResultUtil.ok(reportService.warnDistribution());
+    }
+    
+    @ApiOperation(value = "考情监控-预警时间趋势")
+    @RequestMapping(value = "/examination_monitor/warn_trend", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "结果信息")})
+    public Result warnTrend() {
+        return ResultUtil.ok(reportService.warnTrend());
+    }
 }

+ 71 - 0
themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorCountBean.java

@@ -0,0 +1,71 @@
+package com.qmth.themis.business.bean.backend;
+
+import java.util.List;
+import java.util.Map;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ExaminationMonitorCountBean {
+	@ApiModelProperty(name = "在线人数")
+	private Long onlineCount;
+	@ApiModelProperty(name = "待考人数")
+	private Long waitingCount;
+	@ApiModelProperty(name = "考试中人数")
+	private Long examingCount;
+	@ApiModelProperty(name = "通讯故障人数")
+	private Long exceptionCount;
+	@ApiModelProperty(name = "预警人数")
+	private Long warnCount;
+
+	@ApiModelProperty(name = "机构在线考试人数")
+	private List<Map<String, Object>> orgExamingCount;
+
+	public Long getOnlineCount() {
+		return onlineCount;
+	}
+
+	public void setOnlineCount(Long onlineCount) {
+		this.onlineCount = onlineCount;
+	}
+
+	public Long getWaitingCount() {
+		return waitingCount;
+	}
+
+	public void setWaitingCount(Long waitingCount) {
+		this.waitingCount = waitingCount;
+	}
+
+	public Long getExamingCount() {
+		return examingCount;
+	}
+
+	public void setExamingCount(Long examingCount) {
+		this.examingCount = examingCount;
+	}
+
+	public Long getExceptionCount() {
+		return exceptionCount;
+	}
+
+	public void setExceptionCount(Long exceptionCount) {
+		this.exceptionCount = exceptionCount;
+	}
+
+	public Long getWarnCount() {
+		return warnCount;
+	}
+
+	public void setWarnCount(Long warnCount) {
+		this.warnCount = warnCount;
+	}
+
+	public List<Map<String, Object>> getOrgExamingCount() {
+		return orgExamingCount;
+	}
+
+	public void setOrgExamingCount(List<Map<String, Object>> orgExamingCount) {
+		this.orgExamingCount = orgExamingCount;
+	}
+
+}

+ 25 - 0
themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorHourWarnCountBean.java

@@ -0,0 +1,25 @@
+package com.qmth.themis.business.bean.backend;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ExaminationMonitorHourWarnCountBean {
+
+	@ApiModelProperty(name = "时间")
+	private String hour;
+	@ApiModelProperty(name = "预警次数")
+	private Long count;
+	public String getHour() {
+		return hour;
+	}
+	public void setHour(String hour) {
+		this.hour = hour;
+	}
+	public Long getCount() {
+		return count;
+	}
+	public void setCount(Long count) {
+		this.count = count;
+	}
+
+	
+}

+ 28 - 0
themis-business/src/main/java/com/qmth/themis/business/bean/backend/ExaminationMonitorWarnDistributionBean.java

@@ -0,0 +1,28 @@
+package com.qmth.themis.business.bean.backend;
+
+import java.util.List;
+import java.util.Map;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ExaminationMonitorWarnDistributionBean {
+
+	@ApiModelProperty(name = "机构预警分布")
+	private List<Map<String, Object>> orgDistribution;
+	@ApiModelProperty(name = "预警类型分布")
+	private List<Map<String, Object>> typeDistribution;
+	public List<Map<String, Object>> getOrgDistribution() {
+		return orgDistribution;
+	}
+	public void setOrgDistribution(List<Map<String, Object>> orgDistribution) {
+		this.orgDistribution = orgDistribution;
+	}
+	public List<Map<String, Object>> getTypeDistribution() {
+		return typeDistribution;
+	}
+	public void setTypeDistribution(List<Map<String, Object>> typeDistribution) {
+		this.typeDistribution = typeDistribution;
+	}
+
+	
+}

+ 13 - 5
themis-business/src/main/java/com/qmth/themis/business/bean/backend/InvigilateListDetailBean.java

@@ -2,14 +2,10 @@ package com.qmth.themis.business.bean.backend;
 
 import com.qmth.themis.business.entity.TEExamStudentLog;
 import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.ExamTypeEnum;
-import com.qmth.themis.business.enums.ExceptionEnum;
-import com.qmth.themis.business.enums.VerifyExceptionEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
 import java.io.Serializable;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -73,9 +69,20 @@ public class InvigilateListDetailBean implements Serializable {
     @ApiModelProperty(name = "是否违纪,0:是,1:不是")
     private Integer breachStatus;
 
+    @ApiModelProperty(value = "考生底照地址")
+    private String basePhotoPath;
+
     @ApiModelProperty(name = "考生轨迹")
     private List<TEExamStudentLog> examStudentLogList;
 
+    public String getBasePhotoPath() {
+        return basePhotoPath;
+    }
+
+    public void setBasePhotoPath(String basePhotoPath) {
+        this.basePhotoPath = basePhotoPath;
+    }
+
     public List<TEExamStudentLog> getExamStudentLogList() {
         return examStudentLogList;
     }
@@ -84,7 +91,7 @@ public class InvigilateListDetailBean implements Serializable {
         this.examStudentLogList = examStudentLogList;
     }
 
-    public InvigilateListDetailBean(String examName, String examActivityCode, Long examId, Long examActivityId, Long examStudentId, Long examRecordId, String identity, String examStudentName, String courseNameCode, ExamRecordStatusEnum statusCode, String roomCode, String roomName, Integer breachStatus) {
+    public InvigilateListDetailBean(String examName, String examActivityCode, Long examId, Long examActivityId, Long examStudentId, Long examRecordId, String identity, String examStudentName, String courseNameCode, ExamRecordStatusEnum statusCode, String roomCode, String roomName, Integer breachStatus, String basePhotoPath) {
         this.examName = examName;
         this.examActivityCode = examActivityCode;
         this.examId = examId;
@@ -98,6 +105,7 @@ public class InvigilateListDetailBean implements Serializable {
         this.roomCode = roomCode;
         this.roomName = roomName;
         this.breachStatus = breachStatus;
+        this.basePhotoPath = basePhotoPath;
     }
 
     public String getExamName() {

+ 2 - 1
themis-business/src/main/java/com/qmth/themis/business/dao/TIeExamInvigilateCallMapper.java

@@ -2,6 +2,7 @@ package com.qmth.themis.business.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.qmth.themis.business.dto.response.TIeExamInvigilateCallDto;
 import com.qmth.themis.business.entity.TIeExamInvigilateCall;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -29,7 +30,7 @@ public interface TIeExamInvigilateCallMapper extends BaseMapper<TIeExamInvigilat
      * @param callStatus
      * @return
      */
-    public IPage<TIeExamInvigilateCall> examInvigilateCallQuery(IPage<Map> iPage, @Param("examId") Long examId, @Param("userId") Long userId, @Param("orgId") Long orgId, @Param("status") String status, @Param("callStatus") String callStatus);
+    public IPage<TIeExamInvigilateCallDto> examInvigilateCallQuery(IPage<Map> iPage, @Param("examId") Long examId, @Param("userId") Long userId, @Param("orgId") Long orgId, @Param("status") String status, @Param("callStatus") String callStatus);
 
     /**
      * 监考监控通话count查询

+ 29 - 6
themis-business/src/main/java/com/qmth/themis/business/dao/TOeExamRecordMapper.java

@@ -1,15 +1,24 @@
 package com.qmth.themis.business.dao;
 
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.qmth.themis.business.bean.backend.*;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorHourWarnCountBean;
+import com.qmth.themis.business.bean.backend.InvigilateListBean;
+import com.qmth.themis.business.bean.backend.InvigilateListHistoryBean;
+import com.qmth.themis.business.bean.backend.InvigilateListPatrolBean;
+import com.qmth.themis.business.bean.backend.InvigilateListPatrolReportBean;
+import com.qmth.themis.business.bean.backend.InvigilateListProgressBean;
+import com.qmth.themis.business.bean.backend.InvigilateListVideoBean;
+import com.qmth.themis.business.bean.backend.InvigilateListWarningBean;
 import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
 import com.qmth.themis.business.entity.TOeExamRecord;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
-import java.util.Map;
 
 /**
  * @Description: 考试记录 Mapper 接口
@@ -273,4 +282,18 @@ public interface TOeExamRecordMapper extends BaseMapper<TOeExamRecord> {
     public List<InvigilateListPatrolReportBean> examInvigilateReport(@Param("examId") Long examId, @Param("userId") Long userId);
 
 	public TOeExamRecord findOneByExamId(@Param("examId")Long examId);
+
+	public Long getExamingCount();
+	
+	public Long getExceptionCount();
+	
+	public Long getWarnCount();
+	
+	public List<Map<String,Object>> getOrgExamingCount();
+	
+	public List<Map<String,Object>> getOrgDistribution();
+	
+	public List<Map<String,Object>> getTypeDistribution();
+	
+	public List<ExaminationMonitorHourWarnCountBean> getWarnTrend(@Param("startTime")Date startTime);
 }

+ 38 - 0
themis-business/src/main/java/com/qmth/themis/business/dto/response/TIeExamInvigilateCallDto.java

@@ -0,0 +1,38 @@
+package com.qmth.themis.business.dto.response;
+
+import com.qmth.themis.business.entity.TIeExamInvigilateCall;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+* @Description: 监考 监控通话申请 dto
+* @Param:
+* @return:
+* @Author: wangliang
+* @Date: 2020/9/17
+*/
+public class TIeExamInvigilateCallDto extends TIeExamInvigilateCall implements Serializable{
+
+    @ApiModelProperty(value = "考生姓名")
+    private String examStudentName;
+
+    @ApiModelProperty(value = "考生底照地址")
+    private String basePhotoPath;
+
+    public String getExamStudentName() {
+        return examStudentName;
+    }
+
+    public void setExamStudentName(String examStudentName) {
+        this.examStudentName = examStudentName;
+    }
+
+    public String getBasePhotoPath() {
+        return basePhotoPath;
+    }
+
+    public void setBasePhotoPath(String basePhotoPath) {
+        this.basePhotoPath = basePhotoPath;
+    }
+}

+ 23 - 0
themis-business/src/main/java/com/qmth/themis/business/entity/TEExamStudentLog.java

@@ -54,6 +54,10 @@ public class TEExamStudentLog implements Serializable {
     @TableField(value = "create_time", fill = FieldFill.INSERT)
     private Date createTime;
 
+    @ApiModelProperty(value = "业务id")
+    @TableField(value = "obj_id")
+    private Long objId;
+
     public TEExamStudentLog() {
 
     }
@@ -76,6 +80,25 @@ public class TEExamStudentLog implements Serializable {
         this.examRecordId = examRecordId;
     }
 
+    public TEExamStudentLog(String type, String info, String remark, Long studentId, Long examStudentId, Long examRecordId,Long objId) {
+        this.id = Constants.idGen.next();
+        this.type = type;
+        this.info = info;
+        this.remark = remark;
+        this.studentId = studentId;
+        this.examStudentId = examStudentId;
+        this.examRecordId = examRecordId;
+        this.objId = objId;
+    }
+
+    public Long getObjId() {
+        return objId;
+    }
+
+    public void setObjId(Long objId) {
+        this.objId = objId;
+    }
+
     public Long getExamStudentId() {
         return examStudentId;
     }

+ 5 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/MqGroupEnum.java

@@ -34,6 +34,11 @@ public enum MqGroupEnum {
      */
     WEBSOCKET_OE_GROUP("themis-group-exam-websocketOe"),
 
+    /**
+     * websocket oe 考生 group
+     */
+    WEBSOCKET_OE_MOBILE_GROUP("themis-group-exam-websocketOeMobile"),
+
     /**
      * websocket超时退出 考生 group
      */

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

@@ -42,7 +42,7 @@ public enum MqTagEnum {
     EXAM_SCORE_CALCULATE("重新算分标签", "重新算分", "normal", 30),
     EXAM_STUDNET_UPDATE("考生数据更新标签", "考生数据更新", "normal", 31),
     EXAM_BREAK("考试断点标签", "考试断点", "normal", 32),
-    EXAM_STOP("考试移动端监控暂停标签", "考试移动端监控暂停", "normal", 33);
+    EXAM_STOP("考试移动端监控退出标签", "考试移动端退出暂停", "normal", 33);
 
     private MqTagEnum(String desc, String code, String type, int id) {
         this.desc = desc;

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

@@ -2,6 +2,7 @@ package com.qmth.themis.business.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.themis.business.dto.response.TIeExamInvigilateCallDto;
 import com.qmth.themis.business.entity.TIeExamInvigilateCall;
 
 import java.util.Map;
@@ -26,7 +27,7 @@ public interface TIeExamInvigilateCallService extends IService<TIeExamInvigilate
      * @param callStatus
      * @return
      */
-    public IPage<TIeExamInvigilateCall> examInvigilateCallQuery(IPage<Map> iPage, Long examId, Long userId, Long orgId, String status, String callStatus);
+    public IPage<TIeExamInvigilateCallDto> examInvigilateCallQuery(IPage<Map> iPage, Long examId, Long userId, Long orgId, String status, String callStatus);
 
     /**
      * 监考监控通话count查询

+ 15 - 3
themis-business/src/main/java/com/qmth/themis/business/service/TIeReportService.java

@@ -1,14 +1,17 @@
 package com.qmth.themis.business.service;
 
+import java.util.List;
+import java.util.Map;
+
 import com.qmth.themis.business.base.BasePage;
 import com.qmth.themis.business.bean.backend.ExamBreachDetailListBean;
 import com.qmth.themis.business.bean.backend.ExamExceptionDetailListBean;
 import com.qmth.themis.business.bean.backend.ExamStudentLogDetailListBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorCountBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorHourWarnCountBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorWarnDistributionBean;
 import com.qmth.themis.business.bean.backend.InvigilateListPatrolReportBean;
 
-import java.util.List;
-import java.util.Map;
-
 public interface TIeReportService {
     public Map<String, Object> examView(Long examId, Long examActivityId, String roomCode, String courseCode,
                                         String name, String identity);
@@ -59,4 +62,13 @@ public interface TIeReportService {
      * @return
      */
     public List<InvigilateListPatrolReportBean> examInvigilateReport(Long examId, Long userId);
+
+	/**考情监控-人数
+	 * @return
+	 */
+	public ExaminationMonitorCountBean examinationMonitorCount();
+
+	public ExaminationMonitorWarnDistributionBean warnDistribution();
+
+	public List<ExaminationMonitorHourWarnCountBean> warnTrend();
 }

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

@@ -3,6 +3,7 @@ package com.qmth.themis.business.service.impl;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.dao.TIeExamInvigilateCallMapper;
+import com.qmth.themis.business.dto.response.TIeExamInvigilateCallDto;
 import com.qmth.themis.business.entity.TIeExamInvigilateCall;
 import com.qmth.themis.business.service.TIeExamInvigilateCallService;
 import org.springframework.stereotype.Service;
@@ -35,7 +36,7 @@ public class TIeExamInvigilateCallServiceImpl extends ServiceImpl<TIeExamInvigil
      * @return
      */
     @Override
-    public IPage<TIeExamInvigilateCall> examInvigilateCallQuery(IPage<Map> iPage, Long examId, Long userId, Long orgId, String status, String callStatus) {
+    public IPage<TIeExamInvigilateCallDto> examInvigilateCallQuery(IPage<Map> iPage, Long examId, Long userId, Long orgId, String status, String callStatus) {
         return tIeExamInvigilateCallMapper.examInvigilateCallQuery(iPage, examId, userId, orgId, status, callStatus);
     }
 

+ 476 - 350
themis-business/src/main/java/com/qmth/themis/business/service/impl/TIeReportServiceImpl.java

@@ -1,371 +1,497 @@
 package com.qmth.themis.business.service.impl;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.Resource;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.stereotype.Service;
+
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.OrderItem;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.google.common.collect.Lists;
 import com.qmth.themis.business.base.BasePage;
-import com.qmth.themis.business.bean.backend.*;
+import com.qmth.themis.business.bean.backend.ExamBreachDetailListBean;
+import com.qmth.themis.business.bean.backend.ExamBreachListBean;
+import com.qmth.themis.business.bean.backend.ExamDeficiencyListBean;
+import com.qmth.themis.business.bean.backend.ExamExceptionDetailListBean;
+import com.qmth.themis.business.bean.backend.ExamExceptionListBean;
+import com.qmth.themis.business.bean.backend.ExamReexamListBean;
+import com.qmth.themis.business.bean.backend.ExamStudentLogDetailListBean;
+import com.qmth.themis.business.bean.backend.ExamStudentLogListBean;
+import com.qmth.themis.business.bean.backend.ExamViewCountListBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorCountBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorHourWarnCountBean;
+import com.qmth.themis.business.bean.backend.ExaminationMonitorWarnDistributionBean;
+import com.qmth.themis.business.bean.backend.InvigilateListPatrolReportBean;
 import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
 import com.qmth.themis.business.cache.bean.ExamCacheBean;
-import com.qmth.themis.business.dao.*;
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.dao.TEExamBreachLogMapper;
+import com.qmth.themis.business.dao.TEExamReexamMapper;
+import com.qmth.themis.business.dao.TEExamStudentLogMapper;
+import com.qmth.themis.business.dao.TEExamStudentMapper;
+import com.qmth.themis.business.dao.TIeInvigilateExceptionInfoMapper;
+import com.qmth.themis.business.dao.TOeExamRecordMapper;
 import com.qmth.themis.business.entity.TEExamActivity;
 import com.qmth.themis.business.enums.BreachTypeEnum;
 import com.qmth.themis.business.enums.ExceptionEnum;
+import com.qmth.themis.business.enums.VerifyExceptionEnum;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamService;
 import com.qmth.themis.business.service.TIeReportService;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.*;
-import java.util.stream.Collectors;
+import com.qmth.themis.business.util.RedisUtil;
 
 @Service
 public class TIeReportServiceImpl implements TIeReportService {
 
-    @Resource
-    TEExamStudentMapper examStudentMapper;
-
-    @Resource
-    TOeExamRecordMapper examRecordMapper;
-
-    @Resource
-    TEExamActivityService examActivityService;
-
-    @Resource
-    TEExamService examService;
-
-    @Resource
-    TIeInvigilateExceptionInfoMapper invigilateExceptionInfoMapper;
-
-    @Resource
-    TEExamReexamMapper examReexamMapper;
-
-    @Resource
-    TEExamBreachLogMapper examBreachLogMapper;
-
-    @Resource
-    TEExamStudentLogMapper examStudentLogMapper;
-
-    @Resource
-    TOeExamRecordMapper tOeExamRecordMapper;
-
-    @Override
-    public Map<String, Object> examView(Long examId, Long examActivityId, String roomCode, String courseCode,
-                                        String name, String identity) {
-        //应考人数
-        Long totalNum = 0L;
-        List<Map<String, Object>> total = examStudentMapper.getTotalCount(examId, examActivityId, roomCode, courseCode);
-        Map<Long, Long> totalMap = new HashMap<Long, Long>();
-        for (Map<String, Object> map : total) {
-            Long acId = (Long) map.get("activityId");
-            Long count = (Long) map.get("cc");
-            totalMap.put(acId, count);
-            totalNum = totalNum + count;
-        }
-        //实考人数
-        Long doneNum = 0L;
-        List<Map<String, Object>> doneCount = examRecordMapper.getDoneCount(examId, examActivityId, roomCode, courseCode);
-        Map<Long, Long> doneMap = new HashMap<Long, Long>();
-        for (Map<String, Object> map : doneCount) {
-            Long acId = (Long) map.get("activityId");
-            if (acId != null) {
-                Long count = (Long) map.get("cc");
-                doneMap.put(acId, count);
-                doneNum = doneNum + count;
-            }
-
-        }
-        //缺考人数
-        Map<Long, Long> absentMap = new HashMap<Long, Long>();
-        Date now = new Date();
-        for (Long acId : totalMap.keySet()) {
-            ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(acId);
-            Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000);
-            if (now.getTime() > end) {//场次开考时间结束,未开考的都是缺考
-                Long done = doneMap.get(acId);
-                if (done == null) {
-                    done = 0L;
-                }
-                absentMap.put(acId, totalMap.get(acId) - done);
-            }
-        }
-        Long absentNum = 0L;
-        if (absentMap.size() > 0) {
-            for (Long c : absentMap.values()) {
-                absentNum = absentNum + c;
-            }
-        }
-        //每日已考人数
-        List<Map<String, Object>> doneCountByDay = examRecordMapper.getDoneCountByDay(examId, examActivityId, roomCode, courseCode);
-
-        Map<String, Object> ret = new HashMap<String, Object>();
-        ret.put("examTotal", totalNum);
-        ret.put("actualExamTotal", doneNum);
-        ret.put("deficiencyExamTotal", absentNum);
-        ret.put("completeOffExamTotal", totalNum - doneNum - absentNum);
-        ret.put("examTotalList", doneCountByDay);
-
-        return ret;
-    }
-
-    @Override
-    public BasePage examViewCount(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
-                                  String identity, int pageNumber, int pageSize) {
-        //应考人数
-        IPage<ExamViewCountListBean> total = examStudentMapper.getTotalCountInfo(new Page<>(pageNumber, pageSize), examId, examActivityId, roomCode, courseCode);
-        List<ExamViewCountListBean> data = total.getRecords();
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        if (data == null || data.size() == 0) {
-            return basePage;
-        }
-        List<Long> activityIds = data.stream().map(e -> e.getExamActivityId()).collect(Collectors.toList());
-
-        //实考
-        List<Map<String, Object>> doneCountByDay = examStudentMapper.getDoneCountByActivityIds(examId, activityIds);
-        Map<String, Long> doneMap = new HashMap<String, Long>();
-        for (Map<String, Object> map : doneCountByDay) {
-            Long acId = (Long) map.get("activityId");
-            String roomcode = (String) map.get("roomCode");
-            Long count = (Long) map.get("cc");
-            doneMap.put(acId + "-" + roomcode, count);
-        }
-
-        //缺考
-        List<Long> absentActivityIds = new ArrayList<>();
-        Date now = new Date();
-        for (Long acid : activityIds) {
-            ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(acid);
-            Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000);
-            if (now.getTime() > end) {//场次开考时间结束,未开考的都是缺考
-                absentActivityIds.add(acid);
-            }
-        }
-
-        Map<String, Long> absentMap = new HashMap<String, Long>();
-        if (absentActivityIds.size() > 0) {
-            List<Map<String, Object>> absentCountByDay = examStudentMapper.getAbsentCountByActivityIds(examId, absentActivityIds);
-            for (Map<String, Object> map : absentCountByDay) {
-                Long acId = (Long) map.get("activityId");
-                String roomcode = (String) map.get("roomCode");
-                Long count = (Long) map.get("cc");
-                absentMap.put(acId + "-" + roomcode, count);
-            }
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamViewCountListBean b : data) {
-            Long done = doneMap.get(b.getExamActivityId() + "-" + b.getRoomCode());
-            if (done == null) {
-                done = 0L;
-            }
-            Long absent = absentMap.get(b.getExamActivityId() + "-" + b.getRoomCode());
-            if (absent == null) {
-                absent = 0L;
-            }
-            b.setActualExamTotal(done);
-            b.setDeficiencyExamTotal(absent);
-            b.setExamName(exam.getName());
-        }
-        basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public BasePage examDeficiencyList(Long examId, Long examActivityId, String roomCode, String courseCode,
-                                       String name, String identity, int pageNumber, int pageSize) {
-        Page<ExamDeficiencyListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("t.exam_activity_id"));
-        ipage.addOrder(OrderItem.asc("t.room_code"));
-        QueryWrapper<TEExamActivity> wp = new QueryWrapper<>();
-        wp.lambda().eq(TEExamActivity::getExamId, examId);
-        List<TEExamActivity> acs = examActivityService.list(wp);
-        List<Long> absentActivityIds = new ArrayList<>();
-        Date now = new Date();
-        for (TEExamActivity ac : acs) {
-            Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000L);
-            if (now.getTime() > end) {//场次开考时间结束,未开考的都是缺考
-                absentActivityIds.add(ac.getId());
-            }
-        }
-        if (absentActivityIds.size() == 0) {
-            BasePage basePage = new BasePage(Lists.newArrayList(), 1, pageSize, 0);
-            return basePage;
-        }
-        IPage<ExamDeficiencyListBean> total = examStudentMapper.getExamDeficiencyPage(ipage, examId, examActivityId, roomCode, courseCode, name, identity, absentActivityIds);
-        List<ExamDeficiencyListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamDeficiencyListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public BasePage examExceptionList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
-                                      String identity, int pageNumber, int pageSize) {
-        Page<ExamExceptionListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("f.exam_student_id"));
-        IPage<ExamExceptionListBean> total = invigilateExceptionInfoMapper.getExamExceptionPage(ipage, examId, examActivityId, roomCode, courseCode, name, identity);
-        List<ExamExceptionListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamExceptionListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public List<ExamExceptionDetailListBean> examExceptionDetailList(Long examStudentId) {
-        List<ExamExceptionDetailListBean> ret = invigilateExceptionInfoMapper.getExamExceptionDetailList(examStudentId);
-        if (ret != null && ret.size() > 0) {
-            for (ExamExceptionDetailListBean b : ret) {
-                b.setReason(ExceptionEnum.valueOf(b.getReason()).getCode());
-            }
-        }
-        return ret;
-    }
-
-    @Override
-    public BasePage examReexamList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
-                                   String identity, int pageNumber, int pageSize) {
-        Page<ExamReexamListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("f.exam_student_id"));
-        IPage<ExamReexamListBean> total = examReexamMapper.getExamReexamPage(ipage, examId, examActivityId, roomCode, courseCode, name, identity);
-        List<ExamReexamListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamReexamListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public BasePage examBreachList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
-                                   String identity, int pageNumber, int pageSize) {
-        Page<ExamBreachListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("f.exam_student_id"));
-        IPage<ExamBreachListBean> total = examBreachLogMapper.getExamBreachPage(ipage, examId, examActivityId, roomCode, courseCode, name, identity);
-        List<ExamBreachListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamBreachListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public List<ExamBreachDetailListBean> examBreachListDetail(Long examStudentId) {
-        List<ExamBreachDetailListBean> ret = examBreachLogMapper.getExamBreachDetailList(examStudentId);
-        if (ret != null && ret.size() > 0) {
-            for (ExamBreachDetailListBean b : ret) {
-                if (StringUtils.isNotBlank(b.getType())) {
-                    b.setType(BreachTypeEnum.valueOf(b.getType()).getCode());
-                }
-            }
-        }
-        return ret;
-    }
-
-    @Override
-    public BasePage examRevokeBreachList(Long examId, Long examActivityId, String roomCode, String courseCode,
-                                         String name, String identity, int pageNumber, int pageSize) {
-        Page<ExamBreachListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("f.exam_student_id"));
-        IPage<ExamBreachListBean> total = examBreachLogMapper.getExamRevokeBreachPage(ipage, examId, examActivityId, roomCode, courseCode, name, identity);
-        List<ExamBreachListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamBreachListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public List<ExamBreachDetailListBean> examRevokeBreachListDetail(Long examStudentId) {
-        List<ExamBreachDetailListBean> ret = examBreachLogMapper.getExamBreachDetailList(examStudentId);
-        if (ret != null && ret.size() > 0) {
-            for (ExamBreachDetailListBean b : ret) {
-                if (StringUtils.isNotBlank(b.getType())) {
-                    b.setType(BreachTypeEnum.valueOf(b.getType()).getCode());
-                }
-            }
-        }
-        return ret;
-    }
-
-    @Override
-    public BasePage examStudentLogList(Long examId, Long examActivityId, String roomCode, String courseCode,
-                                       String name, String identity, int pageNumber, int pageSize) {
-        Page<ExamStudentLogListBean> ipage = new Page<>(pageNumber, pageSize);
-        ipage.addOrder(OrderItem.asc("t.id"));
-        IPage<ExamStudentLogListBean> total = examStudentMapper.getPageForStudentLog(ipage, examId, examActivityId, roomCode, courseCode, name, identity);
-        List<ExamStudentLogListBean> data = total.getRecords();
-        if (data == null || data.size() == 0) {
-            BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-            return basePage;
-        }
-        ExamCacheBean exam = examService.getExamCacheBean(examId);
-        for (ExamStudentLogListBean b : data) {
-            b.setExamName(exam.getName());
-        }
-        BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
-        return basePage;
-    }
-
-    @Override
-    public List<ExamStudentLogDetailListBean> examStudentLogListDetail(Long examStudentId) {
-        return examStudentLogMapper.getExamStudentLogList(examStudentId);
-    }
-
-    /**
-     * 在线巡考报表接口
-     *
-     * @param examId
-     * @param userId
-     * @return
-     */
-    @Override
-    public List<InvigilateListPatrolReportBean> patrolReport(Long examId, Long userId) {
-        return tOeExamRecordMapper.patrolReport(examId, userId);
-    }
-
-    /**
-     * 考情监控报表接口
-     *
-     * @param examId
-     * @param userId
-     * @return
-     */
-    @Override
-    public List<InvigilateListPatrolReportBean> examInvigilateReport(Long examId, Long userId) {
-        return tOeExamRecordMapper.examInvigilateReport(examId, userId);
-    }
+	@Resource
+	TEExamStudentMapper examStudentMapper;
+
+	@Resource
+	TOeExamRecordMapper examRecordMapper;
+
+	@Resource
+	TEExamActivityService examActivityService;
+
+	@Resource
+	TEExamService examService;
+
+	@Resource
+	TIeInvigilateExceptionInfoMapper invigilateExceptionInfoMapper;
+
+	@Resource
+	TEExamReexamMapper examReexamMapper;
+
+	@Resource
+	TEExamBreachLogMapper examBreachLogMapper;
+
+	@Resource
+	TEExamStudentLogMapper examStudentLogMapper;
+
+	@Resource
+	TOeExamRecordMapper tOeExamRecordMapper;
+
+	@Resource
+	private RedisUtil redisUtil;
+
+	@Override
+	public Map<String, Object> examView(Long examId, Long examActivityId, String roomCode, String courseCode,
+			String name, String identity) {
+		// 应考人数
+		Long totalNum = 0L;
+		List<Map<String, Object>> total = examStudentMapper.getTotalCount(examId, examActivityId, roomCode, courseCode);
+		Map<Long, Long> totalMap = new HashMap<Long, Long>();
+		for (Map<String, Object> map : total) {
+			Long acId = (Long) map.get("activityId");
+			Long count = (Long) map.get("cc");
+			totalMap.put(acId, count);
+			totalNum = totalNum + count;
+		}
+		// 实考人数
+		Long doneNum = 0L;
+		List<Map<String, Object>> doneCount = examRecordMapper.getDoneCount(examId, examActivityId, roomCode,
+				courseCode);
+		Map<Long, Long> doneMap = new HashMap<Long, Long>();
+		for (Map<String, Object> map : doneCount) {
+			Long acId = (Long) map.get("activityId");
+			if (acId != null) {
+				Long count = (Long) map.get("cc");
+				doneMap.put(acId, count);
+				doneNum = doneNum + count;
+			}
+
+		}
+		// 缺考人数
+		Map<Long, Long> absentMap = new HashMap<Long, Long>();
+		Date now = new Date();
+		for (Long acId : totalMap.keySet()) {
+			ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(acId);
+			Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000);
+			if (now.getTime() > end) {// 场次开考时间结束,未开考的都是缺考
+				Long done = doneMap.get(acId);
+				if (done == null) {
+					done = 0L;
+				}
+				absentMap.put(acId, totalMap.get(acId) - done);
+			}
+		}
+		Long absentNum = 0L;
+		if (absentMap.size() > 0) {
+			for (Long c : absentMap.values()) {
+				absentNum = absentNum + c;
+			}
+		}
+		// 每日已考人数
+		List<Map<String, Object>> doneCountByDay = examRecordMapper.getDoneCountByDay(examId, examActivityId, roomCode,
+				courseCode);
+
+		Map<String, Object> ret = new HashMap<String, Object>();
+		ret.put("examTotal", totalNum);
+		ret.put("actualExamTotal", doneNum);
+		ret.put("deficiencyExamTotal", absentNum);
+		ret.put("completeOffExamTotal", totalNum - doneNum - absentNum);
+		ret.put("examTotalList", doneCountByDay);
+
+		return ret;
+	}
+
+	@Override
+	public BasePage examViewCount(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
+			String identity, int pageNumber, int pageSize) {
+		// 应考人数
+		IPage<ExamViewCountListBean> total = examStudentMapper.getTotalCountInfo(new Page<>(pageNumber, pageSize),
+				examId, examActivityId, roomCode, courseCode);
+		List<ExamViewCountListBean> data = total.getRecords();
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		if (data == null || data.size() == 0) {
+			return basePage;
+		}
+		List<Long> activityIds = data.stream().map(e -> e.getExamActivityId()).collect(Collectors.toList());
+
+		// 实考
+		List<Map<String, Object>> doneCountByDay = examStudentMapper.getDoneCountByActivityIds(examId, activityIds);
+		Map<String, Long> doneMap = new HashMap<String, Long>();
+		for (Map<String, Object> map : doneCountByDay) {
+			Long acId = (Long) map.get("activityId");
+			String roomcode = (String) map.get("roomCode");
+			Long count = (Long) map.get("cc");
+			doneMap.put(acId + "-" + roomcode, count);
+		}
+
+		// 缺考
+		List<Long> absentActivityIds = new ArrayList<>();
+		Date now = new Date();
+		for (Long acid : activityIds) {
+			ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(acid);
+			Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000);
+			if (now.getTime() > end) {// 场次开考时间结束,未开考的都是缺考
+				absentActivityIds.add(acid);
+			}
+		}
+
+		Map<String, Long> absentMap = new HashMap<String, Long>();
+		if (absentActivityIds.size() > 0) {
+			List<Map<String, Object>> absentCountByDay = examStudentMapper.getAbsentCountByActivityIds(examId,
+					absentActivityIds);
+			for (Map<String, Object> map : absentCountByDay) {
+				Long acId = (Long) map.get("activityId");
+				String roomcode = (String) map.get("roomCode");
+				Long count = (Long) map.get("cc");
+				absentMap.put(acId + "-" + roomcode, count);
+			}
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamViewCountListBean b : data) {
+			Long done = doneMap.get(b.getExamActivityId() + "-" + b.getRoomCode());
+			if (done == null) {
+				done = 0L;
+			}
+			Long absent = absentMap.get(b.getExamActivityId() + "-" + b.getRoomCode());
+			if (absent == null) {
+				absent = 0L;
+			}
+			b.setActualExamTotal(done);
+			b.setDeficiencyExamTotal(absent);
+			b.setExamName(exam.getName());
+		}
+		basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public BasePage examDeficiencyList(Long examId, Long examActivityId, String roomCode, String courseCode,
+			String name, String identity, int pageNumber, int pageSize) {
+		Page<ExamDeficiencyListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("t.exam_activity_id"));
+		ipage.addOrder(OrderItem.asc("t.room_code"));
+		QueryWrapper<TEExamActivity> wp = new QueryWrapper<>();
+		wp.lambda().eq(TEExamActivity::getExamId, examId);
+		List<TEExamActivity> acs = examActivityService.list(wp);
+		List<Long> absentActivityIds = new ArrayList<>();
+		Date now = new Date();
+		for (TEExamActivity ac : acs) {
+			Long end = ac.getStartTime().getTime() + (ac.getOpeningSeconds() * 1000L);
+			if (now.getTime() > end) {// 场次开考时间结束,未开考的都是缺考
+				absentActivityIds.add(ac.getId());
+			}
+		}
+		if (absentActivityIds.size() == 0) {
+			BasePage basePage = new BasePage(Lists.newArrayList(), 1, pageSize, 0);
+			return basePage;
+		}
+		IPage<ExamDeficiencyListBean> total = examStudentMapper.getExamDeficiencyPage(ipage, examId, examActivityId,
+				roomCode, courseCode, name, identity, absentActivityIds);
+		List<ExamDeficiencyListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamDeficiencyListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public BasePage examExceptionList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
+			String identity, int pageNumber, int pageSize) {
+		Page<ExamExceptionListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("f.exam_student_id"));
+		IPage<ExamExceptionListBean> total = invigilateExceptionInfoMapper.getExamExceptionPage(ipage, examId,
+				examActivityId, roomCode, courseCode, name, identity);
+		List<ExamExceptionListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamExceptionListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public List<ExamExceptionDetailListBean> examExceptionDetailList(Long examStudentId) {
+		List<ExamExceptionDetailListBean> ret = invigilateExceptionInfoMapper.getExamExceptionDetailList(examStudentId);
+		if (ret != null && ret.size() > 0) {
+			for (ExamExceptionDetailListBean b : ret) {
+				b.setReason(ExceptionEnum.valueOf(b.getReason()).getCode());
+			}
+		}
+		return ret;
+	}
+
+	@Override
+	public BasePage examReexamList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
+			String identity, int pageNumber, int pageSize) {
+		Page<ExamReexamListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("f.exam_student_id"));
+		IPage<ExamReexamListBean> total = examReexamMapper.getExamReexamPage(ipage, examId, examActivityId, roomCode,
+				courseCode, name, identity);
+		List<ExamReexamListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamReexamListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public BasePage examBreachList(Long examId, Long examActivityId, String roomCode, String courseCode, String name,
+			String identity, int pageNumber, int pageSize) {
+		Page<ExamBreachListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("f.exam_student_id"));
+		IPage<ExamBreachListBean> total = examBreachLogMapper.getExamBreachPage(ipage, examId, examActivityId, roomCode,
+				courseCode, name, identity);
+		List<ExamBreachListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamBreachListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public List<ExamBreachDetailListBean> examBreachListDetail(Long examStudentId) {
+		List<ExamBreachDetailListBean> ret = examBreachLogMapper.getExamBreachDetailList(examStudentId);
+		if (ret != null && ret.size() > 0) {
+			for (ExamBreachDetailListBean b : ret) {
+				if (StringUtils.isNotBlank(b.getType())) {
+					b.setType(BreachTypeEnum.valueOf(b.getType()).getCode());
+				}
+			}
+		}
+		return ret;
+	}
+
+	@Override
+	public BasePage examRevokeBreachList(Long examId, Long examActivityId, String roomCode, String courseCode,
+			String name, String identity, int pageNumber, int pageSize) {
+		Page<ExamBreachListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("f.exam_student_id"));
+		IPage<ExamBreachListBean> total = examBreachLogMapper.getExamRevokeBreachPage(ipage, examId, examActivityId,
+				roomCode, courseCode, name, identity);
+		List<ExamBreachListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamBreachListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public List<ExamBreachDetailListBean> examRevokeBreachListDetail(Long examStudentId) {
+		List<ExamBreachDetailListBean> ret = examBreachLogMapper.getExamBreachDetailList(examStudentId);
+		if (ret != null && ret.size() > 0) {
+			for (ExamBreachDetailListBean b : ret) {
+				if (StringUtils.isNotBlank(b.getType())) {
+					b.setType(BreachTypeEnum.valueOf(b.getType()).getCode());
+				}
+			}
+		}
+		return ret;
+	}
+
+	@Override
+	public BasePage examStudentLogList(Long examId, Long examActivityId, String roomCode, String courseCode,
+			String name, String identity, int pageNumber, int pageSize) {
+		Page<ExamStudentLogListBean> ipage = new Page<>(pageNumber, pageSize);
+		ipage.addOrder(OrderItem.asc("t.id"));
+		IPage<ExamStudentLogListBean> total = examStudentMapper.getPageForStudentLog(ipage, examId, examActivityId,
+				roomCode, courseCode, name, identity);
+		List<ExamStudentLogListBean> data = total.getRecords();
+		if (data == null || data.size() == 0) {
+			BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+			return basePage;
+		}
+		ExamCacheBean exam = examService.getExamCacheBean(examId);
+		for (ExamStudentLogListBean b : data) {
+			b.setExamName(exam.getName());
+		}
+		BasePage basePage = new BasePage(total.getRecords(), total.getCurrent(), total.getSize(), total.getTotal());
+		return basePage;
+	}
+
+	@Override
+	public List<ExamStudentLogDetailListBean> examStudentLogListDetail(Long examStudentId) {
+		return examStudentLogMapper.getExamStudentLogList(examStudentId);
+	}
+
+	/**
+	 * 在线巡考报表接口
+	 *
+	 * @param examId
+	 * @param userId
+	 * @return
+	 */
+	@Override
+	public List<InvigilateListPatrolReportBean> patrolReport(Long examId, Long userId) {
+		return tOeExamRecordMapper.patrolReport(examId, userId);
+	}
+
+	/**
+	 * 考情监控报表接口
+	 *
+	 * @param examId
+	 * @param userId
+	 * @return
+	 */
+	@Override
+	public List<InvigilateListPatrolReportBean> examInvigilateReport(Long examId, Long userId) {
+		return tOeExamRecordMapper.examInvigilateReport(examId, userId);
+	}
+
+	/**
+	 * 考情监控-人数
+	 */
+	@Override
+	public ExaminationMonitorCountBean examinationMonitorCount() {
+		ExaminationMonitorCountBean ret = new ExaminationMonitorCountBean();
+		Integer onlineCount = (Integer) redisUtil.get(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT);
+		if (onlineCount == null) {
+			onlineCount = 0;
+		}
+		// 在线人数
+		ret.setOnlineCount(Long.valueOf(onlineCount.toString()));
+		// 考试人数
+		Long examingCount = tOeExamRecordMapper.getExamingCount();
+		if (examingCount == null) {
+			examingCount = 0L;
+		}
+		ret.setExamingCount(examingCount);
+
+		// 待考人数
+		ret.setWaitingCount(onlineCount - examingCount);
+
+		// 通讯故障人数
+		ret.setExceptionCount(tOeExamRecordMapper.getExceptionCount());
+		// 预警人数
+		ret.setWarnCount(tOeExamRecordMapper.getWarnCount());
+		// 机构在考人数
+		ret.setOrgExamingCount(tOeExamRecordMapper.getOrgExamingCount());
+		return ret;
+	}
+
+	/**
+	 * 考情监控-预警分布
+	 */
+	@Override
+	public ExaminationMonitorWarnDistributionBean warnDistribution() {
+		ExaminationMonitorWarnDistributionBean ret = new ExaminationMonitorWarnDistributionBean();
+		ret.setOrgDistribution(tOeExamRecordMapper.getOrgDistribution());
+		List<Map<String, Object>> typeList = tOeExamRecordMapper.getTypeDistribution();
+		if (typeList != null && typeList.size() > 0) {
+			for (Map<String, Object> map : typeList) {
+				map.put("type", VerifyExceptionEnum.valueOf((String) map.get("type")).getCode());
+			}
+		}
+		ret.setTypeDistribution(typeList);
+		return ret;
+	}
+
+	/**
+	 * 考情监控-预警时间趋势
+	 */
+	@Override
+	public List<ExaminationMonitorHourWarnCountBean> warnTrend() {
+		List<ExaminationMonitorHourWarnCountBean> ret = new ArrayList<ExaminationMonitorHourWarnCountBean>();
+		SimpleDateFormat sd = new SimpleDateFormat("HH");
+		Date start = null;
+		Date now = new Date();
+		for (int i = 11; i >= 0; i--) {
+			ExaminationMonitorHourWarnCountBean b = new ExaminationMonitorHourWarnCountBean();
+			Date before = DateUtils.addHours(now, -i);
+			b.setHour(sd.format(before));
+			b.setCount(0L);
+			ret.add(b);
+			if (i == 11) {
+				start = before;
+			}
+		}
+		start = DateUtils.setMinutes(start, 0);
+		start = DateUtils.setSeconds(start, 0);
+		start = DateUtils.setMilliseconds(start, 0);
+		List<ExaminationMonitorHourWarnCountBean> list = tOeExamRecordMapper.getWarnTrend(start);
+		if (list != null && list.size() > 0) {
+			Map<String, Long> map = list.stream().collect(Collectors.toMap(ExaminationMonitorHourWarnCountBean::getHour,
+					ExaminationMonitorHourWarnCountBean::getCount, (key1, key2) -> key2));
+			for(ExaminationMonitorHourWarnCountBean b:ret) {
+				Long c=map.get(b.getHour());
+				if(c!=null) {
+					b.setCount(c);
+				}
+				b.setHour(b.getHour()+":00");
+			}
+		}
+		return ret;
+	}
 }

+ 1 - 0
themis-business/src/main/resources/db/init.sql

@@ -1164,6 +1164,7 @@ CREATE TABLE `t_e_exam_student_log` (
   `info` varchar(1000) DEFAULT NULL COMMENT '描述',
   `remark` mediumtext COMMENT '备注',
   `create_time` timestamp NULL COMMENT '创建时间',
+  `obj_id` bigint(20) DEFAULT NULL COMMENT '业务id',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='考生轨迹';
 

+ 11 - 5
themis-business/src/main/resources/mapper/TIeExamInvigilateCallMapper.xml

@@ -2,13 +2,17 @@
 <!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.TIeExamInvigilateCallMapper">
 
-    <select id="examInvigilateCallQuery" resultType="com.qmth.themis.business.entity.TIeExamInvigilateCall">
+    <select id="examInvigilateCallQuery" resultType="com.qmth.themis.business.dto.response.TIeExamInvigilateCallDto">
         select
-            tieic.*
+            tieic.*,
+            tees.name as examStudentName,
+            tes.base_photo_path as basePhotoPath
         from
             t_ie_exam_invigilate_call tieic
         left join t_e_exam_student tees on
             tees.id = tieic.exam_student_id
+        left join t_e_student tes on
+            tes.id = tees.student_id
         left join t_b_exam_invigilate_user tbeiu on
             tbeiu.exam_id = tieic.exam_id
         and tbeiu.room_code = tees.room_code
@@ -35,11 +39,13 @@
         select
         count(1)
         from
-        t_ie_exam_invigilate_call tieic
+            t_ie_exam_invigilate_call tieic
         left join t_e_exam_student tees on
-        tees.id = tieic.exam_student_id
+            tees.id = tieic.exam_student_id
+        left join t_e_student tes on
+            tes.id = tees.student_id
         left join t_b_exam_invigilate_user tbeiu on
-        tbeiu.exam_id = tieic.exam_id
+            tbeiu.exam_id = tieic.exam_id
         and tbeiu.room_code = tees.room_code
         <where>
             <if test="examId != null and examId != ''">

+ 97 - 0
themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml

@@ -594,4 +594,101 @@
 		t.roomName
 		order by t.roomCode
 	</select>
+	
+	<select id="getExamingCount" resultType="java.lang.Long">
+		select count(1) from t_oe_exam_record f
+		where f.status != 'FINISHED' and f.status != 'PERSISTED'
+	</select>
+	<select id="getExceptionCount" resultType="java.lang.Long">
+			SELECT
+			count(DISTINCT(f.id))
+		FROM
+			t_oe_exam_record f
+		LEFT JOIN t_ie_invigilate_exception_info t ON f.id = t.exam_record_id
+		WHERE
+			f. STATUS != 'FINISHED'
+		AND f. STATUS != 'PERSISTED'
+		AND t.id IS NOT NULL
+	</select>
+	<select id="getWarnCount" resultType="java.lang.Long">
+		SELECT
+			count(DISTINCT(f.id))
+		FROM
+			t_oe_exam_record f
+		LEFT JOIN t_ie_invigilate_warn_info t ON f.id = t.exam_record_id
+		WHERE
+			f. STATUS != 'FINISHED'
+		AND f. STATUS != 'PERSISTED'
+		AND t.type!='NONE'
+		AND t.id IS NOT NULL
+	</select>
+	<select id="getOrgExamingCount" resultType="java.util.Map">
+		SELECT
+		o. NAME orgName,
+		tem.cc count
+	FROM
+		(
+			SELECT
+				h.org_id orgId,
+				count(DISTINCT(h.id)) cc
+			FROM
+				t_oe_exam_record f
+			LEFT JOIN t_e_exam_student t ON f.exam_student_id = t.id
+			LEFT JOIN t_e_student h ON t.student_id = h.id
+			WHERE
+				f. STATUS != 'FINISHED'
+			AND f. STATUS != 'PERSISTED'
+			GROUP BY
+				h.org_id
+		) tem
+	LEFT JOIN t_b_org o ON o.id = tem.orgId
+	</select>
+	<select id="getOrgDistribution" resultType="java.util.Map">
+		SELECT
+		o. NAME orgName,
+		tem.cc count
+		FROM
+			(
+				SELECT
+					h.org_id orgId,
+					count(DISTINCT(w.id)) cc
+				FROM
+					t_oe_exam_record f
+				LEFT JOIN t_ie_invigilate_warn_info w ON f.id = w.exam_record_id
+				LEFT JOIN t_e_exam_student t ON f.exam_student_id = t.id
+				LEFT JOIN t_e_student h ON t.student_id = h.id
+				WHERE
+					f. STATUS != 'FINISHED'
+				AND f. STATUS != 'PERSISTED'
+				AND w.id IS NOT NULL
+				AND w.type!='NONE'
+				GROUP BY
+					h.org_id
+			) tem
+		LEFT JOIN t_b_org o ON o.id = tem.orgId
+	</select>
+	<select id="getTypeDistribution" resultType="java.util.Map">
+		SELECT
+			w.type type,
+			count(DISTINCT(w.id)) count
+		FROM
+			t_oe_exam_record f
+		LEFT JOIN t_ie_invigilate_warn_info w ON f.id = w.exam_record_id
+		WHERE
+			f. STATUS != 'FINISHED'
+		AND f. STATUS != 'PERSISTED'
+		AND w.id IS NOT NULL
+		AND w.type!='NONE'
+		GROUP BY
+			w.type
+	</select>
+	<select id="getWarnTrend" resultType="com.qmth.themis.business.bean.backend.ExaminationMonitorHourWarnCountBean">
+			SELECT
+			DATE_FORMAT(w.create_time,'%H') hour,
+			count(1) count
+		FROM  t_ie_invigilate_warn_info w 
+		WHERE w.type!='NONE' and w.create_time&gt;=#{startTime}
+		GROUP BY
+			DATE_FORMAT(w.create_time,'%H')
+	</select>
 </mapper>

+ 11 - 4
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -1,9 +1,11 @@
 package com.qmth.themis.exam.api;
 
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qmth.themis.business.annotation.ApiJsonObject;
 import com.qmth.themis.business.annotation.ApiJsonProperty;
 import com.qmth.themis.business.bean.exam.*;
+import com.qmth.themis.business.cache.ExamBreakCacheUtil;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.MqDto;
@@ -12,10 +14,7 @@ import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
 import com.qmth.themis.business.dto.response.TEExamDto;
 import com.qmth.themis.business.dto.response.TEExamResultDto;
 import com.qmth.themis.business.entity.TEExam;
-import com.qmth.themis.business.enums.MqTagEnum;
-import com.qmth.themis.business.enums.MqTopicEnum;
-import com.qmth.themis.business.enums.SystemOperationEnum;
-import com.qmth.themis.business.enums.WebsocketTypeEnum;
+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.TEExamStudentService;
@@ -126,6 +125,14 @@ public class TEExamController {
             }
             ExamStartBean examStartBean = teExamService.start(teStudent.getId(), param.getRecordId());
             if (Objects.nonNull(param.getReason())) {
+                Long breakId = ExamRecordCacheUtil.getLastBreakId(param.getRecordId());
+                if (Objects.nonNull(breakId)) {
+                    JSONObject jsonObject = JSONObject.parseObject(param.getReason());
+                    ExceptionEnum exceptionEnum = ExceptionEnum.valueOf(ExceptionEnum.convertToName(String.valueOf(jsonObject.getJSONObject("reason").get("type"))));
+                    String reason = String.valueOf(jsonObject.getJSONObject("reason").get("reason"));
+                    ExamBreakCacheUtil.setBreakReason(breakId, exceptionEnum);
+                    ExamBreakCacheUtil.setResumeReason(breakId, reason);
+                }
                 //考试断点异常原因 发送mq start
                 MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXCEPTION_LOG.name(), JacksonUtil.parseJson(param), MqTagEnum.EXCEPTION_LOG, String.valueOf(param.getRecordId()), param.getReason());
                 mqDtoService.assembleSendOneWayMsg(mqDto);

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

@@ -24,7 +24,6 @@ import com.qmth.themis.business.entity.TEConfig;
 import com.qmth.themis.business.entity.TEStudent;
 import com.qmth.themis.business.enums.*;
 import com.qmth.themis.business.service.*;
-import com.qmth.themis.business.util.JacksonUtil;
 import com.qmth.themis.business.util.RedisUtil;
 import com.qmth.themis.business.util.ServletUtil;
 import com.qmth.themis.business.util.SessionUtil;
@@ -209,24 +208,24 @@ public class TEStudentController {
             if (Objects.isNull(status)) {
                 throw new BusinessException("考试状态不能为空");
             }
-            Long examIdMap = ExamRecordCacheUtil.getExamId(recordId);
+            Long ecExamId = ExamRecordCacheUtil.getExamId(recordId);
             Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
             Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
             Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
-            ExamCacheBean ec = teExamService.getExamCacheBean(examIdMap);//考试缓存
+            ExamCacheBean ec = teExamService.getExamCacheBean(ecExamId);//考试缓存
             Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ? 0 : ec.getBreakExpireSeconds();
             ExamActivityCacheBean examActivityCacheBean = teExamActivityService.getExamActivityCacheBean(examActivityId);//考试场次缓存
             ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
 
-            //获取最近同步时间
-            Date clientLastSyncTime = ExamRecordCacheUtil.getClientLastSyncTime(recordId);
+            //获取断点时间
+            Date lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
             //获取剩余断点次数
             Integer alreadyBreakCount = ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
             Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
             leftBreakResumeCount = leftBreakResumeCount <= 0 ? 0 : leftBreakResumeCount;
             //如果断点时间大于整体断点时间,则强制交卷
             if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING) || Objects.equals(status, ExamRecordStatusEnum.BREAK_OFF) || Objects.equals(status, ExamRecordStatusEnum.RESUME_PREPARE)) {
-                if (Objects.nonNull(clientLastSyncTime) && (System.currentTimeMillis() - clientLastSyncTime.getTime()) / 1000 > breakExpireSeconds) {
+                if (Objects.nonNull(lastBreakTime) && (System.currentTimeMillis() - lastBreakTime.getTime()) / 1000 > breakExpireSeconds) {
                     teExamService.finish(teStudentCacheDto.getId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
                     List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
                     if (Objects.nonNull(list) && list.size() > 0) {
@@ -240,17 +239,20 @@ public class TEStudentController {
                         if (Objects.nonNull(list) && list.size() > 0) {
                             map.put("waiting", list);
                         }
-                    } else {//否则断点次数+1
-                        alreadyBreakCount++;
-                        ExamRecordCacheUtil.setLastBreakId(recordId, Constants.idGen.next());
-                        ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.RESUME_PREPARE);
-                        ExamRecordCacheUtil.setLastBreakTime(recordId);
-                        ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount);
-                        ExamRecordCacheUtil.setLastStartTime(recordId);
-                        //考试断点异常原因 发送mq start
-                        MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
-                        mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
-                        //考试断点异常原因 发送mq end
+                    } else {
+                        //只有ANSWERING状态才生成断点
+                        if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
+                            alreadyBreakCount++;
+                            ExamRecordCacheUtil.setLastBreakId(recordId, Constants.idGen.next());
+                            ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.RESUME_PREPARE);
+                            ExamRecordCacheUtil.setLastBreakTime(recordId);
+                            ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount);
+                            ExamRecordCacheUtil.setLastStartTime(recordId);
+                            //考试断点异常原因 发送mq start
+                            MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
+                            mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
+                            //考试断点异常原因 发送mq end
+                        }
                         ExamUnFinishBean examUnFinishBean = this.unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean, examStudentId);
                         map.put("unFinished", examUnFinishBean);
                     }

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

@@ -5,6 +5,7 @@ import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.enums.MqGroupEnum;
 import com.qmth.themis.business.enums.MqTagEnum;
 import com.qmth.themis.business.enums.MqTopicEnum;
+import com.qmth.themis.exam.websocket.WebSocketMobileServer;
 import com.qmth.themis.exam.websocket.WebSocketOeServer;
 import com.qmth.themis.mq.listener.RocketMessageConsumer;
 import com.qmth.themis.mq.templete.impl.*;
@@ -56,7 +57,8 @@ public class StartRunning implements CommandLineRunner {
         /**
          * websocket mq start
          */
-        rocketMessageConsumer.setRocketMQConsumer(nameServer, MqGroupEnum.WEBSOCKET_OE_GROUP.getCode(), MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.OE_HARD_FINISH.name() + "||" + MqTagEnum.OE_IM_BROADCASTING.name() + "||" + MqTagEnum.OE_IM_CLUSTERING.name() + "||" + MqTagEnum.OE_LIVENESS_VERIFY.name() + "||" + MqTagEnum.OE_MONITOR_FINISH.name() + "||" + MqTagEnum.OE_WARNING_FINISH.name() + "||" + MqTagEnum.EXAM_STOP.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(WebSocketOeServer.class));
+        rocketMessageConsumer.setRocketMQConsumer(nameServer, MqGroupEnum.WEBSOCKET_OE_GROUP.getCode(), MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.OE_HARD_FINISH.name() + "||" + MqTagEnum.OE_IM_BROADCASTING.name() + "||" + MqTagEnum.OE_IM_CLUSTERING.name() + "||" + MqTagEnum.OE_LIVENESS_VERIFY.name() + "||" + MqTagEnum.OE_MONITOR_FINISH.name() + "||" + MqTagEnum.OE_WARNING_FINISH.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(WebSocketOeServer.class));
+        rocketMessageConsumer.setRocketMQConsumer(nameServer, MqGroupEnum.WEBSOCKET_OE_MOBILE_GROUP.getCode(), MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_STOP.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(WebSocketMobileServer.class));
         rocketMessageConsumer.setRocketMQConsumer(nameServer, MqGroupEnum.WEBSOCKET_DELAY_GROUP.getCode(), MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.OE_UN_NORMAL.name(), MessageModel.CLUSTERING, SpringContextHolder.getBean(WebsocketUnNormalConcurrentlyImpl.class));
         /**
          * websocket mq end

+ 1 - 1
themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketMobileServer.java

@@ -278,7 +278,7 @@ public class WebSocketMobileServer implements Concurrently {
                     mqOeLogicService.execMqMaxReconsumeTime(mqDto, SystemConstant.MQ_TOPIC_BUFFER_LIST);
                 } else {
                     if (Objects.nonNull(mqDto.getAck()) && mqDto.getAck().intValue() != SystemConstant.STANDARD_ACK_TYPE && Objects.nonNull(redisUtil.get(SystemConstant.MQ_TOPIC_BUFFER_LIST, mqDto.getId())) && redisUtil.lock(SystemConstant.REDIS_LOCK_MQ_PREFIX + mqDto.getId(), SystemConstant.REDIS_LOCK_MQ_TIME_OUT)) {
-                        mqOeLogicService.execMqOeLogic(mqDto, SystemConstant.MQ_TOPIC_BUFFER_LIST);
+                        mqOeLogicService.execMqOeMobileLogic(mqDto, SystemConstant.MQ_TOPIC_BUFFER_LIST);
                         return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                     } else {
                         log.info(":{}-:{} 消息ack未确认,重发", threadId, threadName);

+ 62 - 32
themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

@@ -2,15 +2,16 @@ package com.qmth.themis.mq.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.google.gson.Gson;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.cache.RedisKeyHelper;
+import com.qmth.themis.business.cache.bean.ExamCacheBean;
 import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
 import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.MqDto;
 import com.qmth.themis.business.dto.WarningDto;
-import com.qmth.themis.business.dto.WebsocketDto;
 import com.qmth.themis.business.entity.*;
 import com.qmth.themis.business.enums.*;
 import com.qmth.themis.business.service.*;
@@ -34,7 +35,6 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * @Description: mq执行逻辑 impl
@@ -235,24 +235,47 @@ public class MqLogicServiceImpl implements MqLogicService {
                 Long l = (System.currentTimeMillis() - clientLastSyncTime.getTime()) / 1000;
                 if (l >= 2 && !Objects.equals(ExamRecordCacheUtil.getStatus(recordId), ExamRecordStatusEnum.FINISHED)) {
                     diff = l.intValue();
-                    //是否增加断点记录或者交卷处理统一放登录逻辑里处理,这里只更新考试记录状态为中断
                     ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF);
                 }
             }
-            //发送考试暂停mq消息 start
-            MqDto mqDtoExamStop = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_STOP.name(), recordId, MqTagEnum.EXAM_STOP, String.valueOf(recordId), String.valueOf(recordId));
-            mqDtoService.assembleSendOneWayMsg(mqDtoExamStop);
-            //发送考试暂停mq消息 end
-
-            //增加异常日志
+            ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
             Long examId = ExamRecordCacheUtil.getExamId(recordId);
             Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
             Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
             ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
-            TIeInvigilateExceptionInfo tIeInvigilateExceptionInfo = new TIeInvigilateExceptionInfo(examId, examActivityId, recordId, examStudentId, ExceptionEnum.NET_TIME_OUT.getCode(), ExceptionEnum.NET_TIME_OUT, diff);
-            tIeInvigilateExceptionInfoService.saveOrUpdate(tIeInvigilateExceptionInfo);
-            TEExamStudentLog teExamStudentLog = new TEExamStudentLog(SystemOperationEnum.BREAK_OFF.name(), SystemOperationEnum.BREAK_OFF.getCode(), SystemOperationEnum.BREAK_OFF.getCode(), examStudentCacheBean.getStudentId(), examStudentId, recordId);
-            teExamStudentLogService.save(teExamStudentLog);
+            ExamCacheBean ec = teExamService.getExamCacheBean(examId);//考试缓存
+            Long breakId = null;
+            if (!Objects.equals(status, ExamRecordStatusEnum.FIRST_PREPARE)) {
+                //增加断点记录,获取剩余断点次数
+                Integer alreadyBreakCount = ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
+                Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
+                leftBreakResumeCount = leftBreakResumeCount <= 0 ? 0 : leftBreakResumeCount;
+                if (leftBreakResumeCount <= 0) {
+                    Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
+                    teExamService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
+                } else {
+                    alreadyBreakCount++;
+                    breakId = Constants.idGen.next();
+                    ExamRecordCacheUtil.setLastBreakId(recordId, breakId);
+                    ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF);
+                    ExamRecordCacheUtil.setLastBreakTime(recordId);
+                    ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount);
+                    ExamRecordCacheUtil.setLastStartTime(recordId);
+                    //考试断点异常原因 发送mq start
+                    MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
+                    mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
+                    //考试断点异常原因 发送mq end
+                }
+                //增加异常日志
+                TIeInvigilateExceptionInfo tIeInvigilateExceptionInfo = new TIeInvigilateExceptionInfo(examId, examActivityId, recordId, examStudentId, ExceptionEnum.NET_TIME_OUT.getCode(), ExceptionEnum.NET_TIME_OUT, diff);
+                tIeInvigilateExceptionInfoService.saveOrUpdate(tIeInvigilateExceptionInfo);
+                TEExamStudentLog teExamStudentLog = new TEExamStudentLog(SystemOperationEnum.BREAK_OFF.name(), SystemOperationEnum.BREAK_OFF.getCode(), SystemOperationEnum.BREAK_OFF.getCode(), examStudentCacheBean.getStudentId(), examStudentId, recordId, breakId);
+                teExamStudentLogService.save(teExamStudentLog);
+            }
+            //发送移动端监考退出考试mq消息 start
+            MqDto mqDtoExamStop = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_STOP.name(), recordId, MqTagEnum.EXAM_STOP, String.valueOf(recordId), String.valueOf(recordId));
+            mqDtoService.assembleSendOneWayMsg(mqDtoExamStop);
+            //发送移动端监考退出考试mq消息 end
         }
         TMRocketMessage tmRocketMessage = gson.fromJson(gson.toJson(mqDto), TMRocketMessage.class);
         Map map = new HashMap();
@@ -481,25 +504,32 @@ public class MqLogicServiceImpl implements MqLogicService {
             JSONObject jsonObject = JSONObject.parseObject(String.valueOf(mqDto.getBody()));
             ExceptionEnum exceptionEnum = ExceptionEnum.valueOf(ExceptionEnum.convertToName(String.valueOf(jsonObject.getJSONObject("reason").get("type"))));
             Long recordId = Long.parseLong(mqDto.getObjId());
-            //获取最近同步时间
-            Date clientLastSyncTime = ExamRecordCacheUtil.getClientLastSyncTime(recordId);
-            Integer diff = 0;
-            if (Objects.nonNull(clientLastSyncTime)) {
-                Long l = ((System.currentTimeMillis() - clientLastSyncTime.getTime()) / 1000);
-                diff = l.intValue();
+            Long breakId = ExamRecordCacheUtil.getLastBreakId(recordId);
+            if (Objects.nonNull(breakId)) {
+                //获取断点时间
+                Date lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
+                Integer diff = 0;
+                if (Objects.nonNull(lastBreakTime)) {
+                    Long l = ((System.currentTimeMillis() - lastBreakTime.getTime()) / 1000);
+                    diff = l.intValue();
+                }
+                //修改异常日志
+                String reason = String.valueOf(jsonObject.getJSONObject("reason").get("reason"));
+                UpdateWrapper<TIeInvigilateExceptionInfo> tIeInvigilateExceptionInfoUpdateWrapper = new UpdateWrapper<>();
+                tIeInvigilateExceptionInfoUpdateWrapper.lambda().set(TIeInvigilateExceptionInfo::getInfo, reason)
+                        .set(TIeInvigilateExceptionInfo::getType, exceptionEnum)
+                        .set(TIeInvigilateExceptionInfo::getDifference, diff)
+                        .eq(TIeInvigilateExceptionInfo::getId, breakId);
+                tIeInvigilateExceptionInfoService.update(tIeInvigilateExceptionInfoUpdateWrapper);
+
+                //修改考生轨迹
+                UpdateWrapper<TEExamStudentLog> teExamStudentLogUpdateWrapper = new UpdateWrapper<>();
+                teExamStudentLogUpdateWrapper.lambda().set(TEExamStudentLog::getType, exceptionEnum.name())
+                        .set(TEExamStudentLog::getInfo, exceptionEnum.getCode())
+                        .set(TEExamStudentLog::getRemark, reason)
+                        .eq(TEExamStudentLog::getObjId, breakId);
+                teExamStudentLogService.update(teExamStudentLogUpdateWrapper);
             }
-            Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
-            ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
-
-            //增加异常日志
-            Long examId = ExamRecordCacheUtil.getExamId(recordId);
-            Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
-            String reason = String.valueOf(jsonObject.getJSONObject("reason").get("reason"));
-            TIeInvigilateExceptionInfo tIeInvigilateExceptionInfo = new TIeInvigilateExceptionInfo(examId, examActivityId, recordId, examStudentId, reason, exceptionEnum, diff);
-            tIeInvigilateExceptionInfoService.saveOrUpdate(tIeInvigilateExceptionInfo);
-
-            TEExamStudentLog teExamStudentLog = new TEExamStudentLog(exceptionEnum.name(), exceptionEnum.getCode(), reason, examStudentCacheBean.getStudentId(), examStudentCacheBean.getId(), recordId);
-            teExamStudentLogService.saveOrUpdate(teExamStudentLog);
         } else if (tag.contains(MqTagEnum.WARNING_LOG.name())) {//考试预警日志
             //todo 预警先预留
         }
@@ -571,7 +601,7 @@ public class MqLogicServiceImpl implements MqLogicService {
         }
         //增加断点记录
         TOeExamBreakHistory tOeExamBreakHistory = new TOeExamBreakHistory(ExamRecordCacheUtil.getLastBreakId(recordId), recordId, new Date(), exceptionEnum, exceptionEnum.name());
-        tOeExamBreakHistoryService.save(tOeExamBreakHistory);
+        tOeExamBreakHistoryService.saveOrUpdate(tOeExamBreakHistory);
         redisUtil.setForHash(RedisKeyHelper.examBreakCacheKey(tOeExamBreakHistory.getId()), SimpleBeanUtil.objectToMap(tOeExamBreakHistory));
 
         Gson gson = new Gson();