wangliang 4 лет назад
Родитель
Сommit
f53d1d1b71

+ 1 - 1
themis-backend/src/main/java/com/qmth/themis/backend/listener/service/impl/MqAdminLogicServiceImpl.java

@@ -17,7 +17,7 @@ import javax.annotation.Resource;
 import java.lang.reflect.InvocationTargetException;
 
 /** 
-* @Description: mq 管理端执行逻辑 impl 
+* @Description: mq 管理端执行逻辑 impl
 * @Param:  
 * @return:  
 * @Author: wangliang

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

@@ -76,4 +76,7 @@ public class ExamRecordCacheUtil {
 	public static Date getClientLastSyncTime(Long recordId) {
 		return (Date) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "clientLastSyncTime");
 	}
+	public static Integer getLeftBreakResumeCount(Long recordId) {
+		return (Integer) redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), "leftBreakResumeCount");
+	}
 }

+ 36 - 0
themis-business/src/main/java/com/qmth/themis/business/dto/response/TEExamUnFinishDto.java

@@ -46,6 +46,42 @@ public class TEExamUnFinishDto implements Serializable {
     private Integer cameraPhotoUpload;//是否允许使用摄像头拍照答题,0:不允许,1:允许
     private Integer wxappPhotoUpload;//是否允许使用微信拍照答题,0:不允许,1:允许
     private Integer wxappVideoRecord;//是否开启微信小程序视频转录,0:不开启,1:开启
+    private Integer breakExpireSeconds;//断点失效时间(秒)
+    private Integer breakResumeCount;//断点续考次数
+    private Integer durationSeconds;//累计考试用时
+    private Date clientLastSyncTime;//客户端最近同步时间
+
+    public Date getClientLastSyncTime() {
+        return clientLastSyncTime;
+    }
+
+    public void setClientLastSyncTime(Date clientLastSyncTime) {
+        this.clientLastSyncTime = clientLastSyncTime;
+    }
+
+    public Integer getDurationSeconds() {
+        return durationSeconds;
+    }
+
+    public void setDurationSeconds(Integer durationSeconds) {
+        this.durationSeconds = durationSeconds;
+    }
+
+    public Integer getBreakExpireSeconds() {
+        return breakExpireSeconds;
+    }
+
+    public void setBreakExpireSeconds(Integer breakExpireSeconds) {
+        this.breakExpireSeconds = breakExpireSeconds;
+    }
+
+    public Integer getBreakResumeCount() {
+        return breakResumeCount;
+    }
+
+    public void setBreakResumeCount(Integer breakResumeCount) {
+        this.breakResumeCount = breakResumeCount;
+    }
 
     public Long getId() {
         return id;

+ 2 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/MqEnum.java

@@ -23,6 +23,8 @@ public enum MqEnum {
     WEBSOCKET_OFFLINE_LOG(9, "websocket强行离线(交卷)"),
     WEBSOCKET_IM_CLUSTERING_LOG(10, "websocket点对点发送消息"),
     WEBSOCKET_IM_BROADCASTING_LOG(11, "websocket广播发送消息"),
+    WEBSOCKET_MONITOR_LOG(12, "监考强制离线(交卷)"),
+    WEBSOCKET_WARNING_LOG(13, "预警强制离线(交卷)"),
 
     /**
      * websocket超时退出

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

@@ -1,7 +1,6 @@
 package com.qmth.themis.business.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
 import com.qmth.themis.business.entity.TOeExamRecord;
 
 import java.util.Map;
@@ -27,8 +26,10 @@ public interface TOeExamRecordService extends IService<TOeExamRecord> {
 
     Long saveByPrepare(Long examId, Long examActivityId, Long examStudentId, Long paperId, Integer serialNumber);
 
-	/**计算客观分
-	 * @param param
-	 */
-	void calculateObjectiveScore(Map<String, Object> param);
+    /**
+     * 计算客观分
+     *
+     * @param param
+     */
+    void calculateObjectiveScore(Map<String, Object> param);
 }

+ 20 - 1
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -2,7 +2,6 @@ package com.qmth.themis.business.service.impl;
 
 import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-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;
@@ -13,6 +12,7 @@ import com.qmth.themis.business.dao.TOeExamRecordMapper;
 import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
 import com.qmth.themis.business.entity.TOeExamRecord;
 import com.qmth.themis.business.enums.ExamRecordStatusEnum;
+import com.qmth.themis.business.enums.FinishTypeEnum;
 import com.qmth.themis.business.enums.ObjectiveScorePolicyEnum;
 import com.qmth.themis.business.service.TEExamPaperService;
 import com.qmth.themis.business.service.TEExamService;
@@ -52,6 +52,9 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     @Resource
     TEExamService examService;
 
+    @Resource
+    TEExamService teExamService;
+
     /**
      * 获取考试未完列表
      *
@@ -64,6 +67,22 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
     public Map getUnFinishExam(Long studentId, Long examId, Long orgId) {
         TEExamUnFinishDto teExamUnFinishDto = tOeExamRecordMapper.getUnFinishExam(studentId, examId, orgId);
         if (Objects.nonNull(teExamUnFinishDto)) {
+            //获取最近同步时间
+            Date clientLastSyncTime = ExamRecordCacheUtil.getClientLastSyncTime(teExamUnFinishDto.getRecordId());
+            //获取剩余断点次数
+            Integer leftBreakResumeCount = ExamRecordCacheUtil.getLeftBreakResumeCount(teExamUnFinishDto.getRecordId());
+            //如果断点时间大于整体断点时间,则强制交卷
+            if ((System.currentTimeMillis() - clientLastSyncTime.getTime() / 1000) > teExamUnFinishDto.getBreakExpireSeconds()) {
+                teExamService.finish(teExamUnFinishDto.getExamStudentId(), teExamUnFinishDto.getRecordId(), FinishTypeEnum.AUTO.name(), (int) (((System.currentTimeMillis() - teExamUnFinishDto.getClientLastSyncTime().getTime()) / 1000) + teExamUnFinishDto.getDurationSeconds()));
+            } else {//否则断点次数加1
+                leftBreakResumeCount++;
+                //如果断点次数超过了考试整体断点次数,也强制交卷
+                if (leftBreakResumeCount > teExamUnFinishDto.getBreakResumeCount()) {
+                    teExamService.finish(teExamUnFinishDto.getExamStudentId(), teExamUnFinishDto.getRecordId(), FinishTypeEnum.AUTO.name(), (int) (((System.currentTimeMillis() - teExamUnFinishDto.getClientLastSyncTime().getTime()) / 1000) + teExamUnFinishDto.getDurationSeconds()));
+                } else {
+                    //发送mq,更新考试记录和加入断点次数记录
+                }
+            }
             Map finalMap = new HashMap();
             Map<String, Object> waitingMap = new HashMap();
             waitingMap.put("id", teExamUnFinishDto.getId());

+ 5 - 1
themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml

@@ -37,7 +37,11 @@
             tee.wxapp_video_push as wxappVideoPush,
             tee.camera_photo_upload as cameraPhotoUpload,
             tee.wxapp_photo_upload as wxappPhotoUpload,
-            tee.wxapp_video_record as wxappVideoRecord
+            tee.wxapp_video_record as wxappVideoRecord,
+            tee.break_expire_seconds as breakExpireSeconds,
+            tee.break_resume_count as breakResumeCount,
+            toer.duration_seconds as durationSeconds,
+            toer.client_last_sync_time as clientLastSyncTime
             from
             t_oe_exam_record toer
             left join t_e_exam_student tees on

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

@@ -3,6 +3,7 @@ package com.qmth.themis.exam.api;
 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.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.AuthDto;
 import com.qmth.themis.business.dto.response.TEExamDto;
@@ -169,14 +170,14 @@ public class TEStudentController {
         String test = SignatureInfo.build(SignatureType.TOKEN, sessionId, token);
         Map<String, Object> map = new HashMap<>();
         //获取未完/待考列表
-        Map TEExamUnFinishDto = tOeExamRecordService.getUnFinishExam(teStudent.getId(), examId, orgId);
-        if (Objects.isNull(TEExamUnFinishDto)) {
+        Map unFinishExam = tOeExamRecordService.getUnFinishExam(teStudent.getId(), examId, orgId);
+        if (Objects.isNull(unFinishExam)) {
             List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
             if (Objects.nonNull(list) && list.size() > 0) {
                 map.put("waiting", list);
             }
         } else {
-            map.put("unFinished", TEExamUnFinishDto);
+            map.put("unFinished", unFinishExam);
         }
         //获取全局考试配置
         TEConfig teConfig = teConfigService.getGlobalConfig();

+ 19 - 16
themis-exam/src/main/java/com/qmth/themis/exam/listener/service/impl/MqOeLogicServiceImpl.java

@@ -19,7 +19,9 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.lang.reflect.InvocationTargetException;
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -74,21 +76,22 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
         Gson gson = new Gson();
         MqEnum mqEnum = mqDto.getType();
         ConcurrentHashMap<String, WebSocketOeServer> webSocketMap = WebSocketOeServer.getWebSocketMap();
-        if (MqEnum.WEBSOCKET_OFFLINE_LOG.ordinal() == mqEnum.ordinal()) {//下线
-            JSONArray jsonArray = JSONArray.parseArray(String.valueOf(mqDto.getBody()));
-            Set<String> examStudentIdentitySet = jsonArray.toJavaObject(Set.class);
-            log.info("examStudentIdentitySet:{}", JacksonUtil.parseJson(examStudentIdentitySet));
-            webSocketMap.forEach((k, v) -> {
-                examStudentIdentitySet.forEach(s -> {
-                    if (k.contains(s)) {
-                        Map map = new HashMap<>();
-                        map.put("offLineId", k);
-                        WebsocketDto websocketDto = new WebsocketDto("offLine", map);
-                        v.sendMessage(websocketDto);
-                    }
-                });
-            });
-        } else if (MqEnum.WEBSOCKET_IM_CLUSTERING_LOG.ordinal() == mqEnum.ordinal()) {//点对点消息
+//        if (MqEnum.WEBSOCKET_OFFLINE_LOG.ordinal() == mqEnum.ordinal()) {//下线
+//            JSONArray jsonArray = JSONArray.parseArray(String.valueOf(mqDto.getBody()));
+//            Set<String> examStudentIdentitySet = jsonArray.toJavaObject(Set.class);
+//            log.info("examStudentIdentitySet:{}", JacksonUtil.parseJson(examStudentIdentitySet));
+//            webSocketMap.forEach((k, v) -> {
+//                examStudentIdentitySet.forEach(s -> {
+//                    if (k.contains(s)) {
+//                        Map map = new HashMap<>();
+//                        map.put("offLineId", k);
+//                        WebsocketDto websocketDto = new WebsocketDto("offLine", map);
+//                        v.sendMessage(websocketDto);
+//                    }
+//                });
+//            });
+//        } else
+        if (MqEnum.WEBSOCKET_IM_CLUSTERING_LOG.ordinal() == mqEnum.ordinal()) {//点对点消息
             JSONArray jsonArray = JSONArray.parseArray(String.valueOf(mqDto.getBody()));
             Set<String> examStudentIdentitySet = jsonArray.toJavaObject(Set.class);
             log.info("examStudentIdentitySet:{}", JacksonUtil.parseJson(examStudentIdentitySet));

+ 11 - 21
themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

@@ -8,6 +8,8 @@ import java.util.Objects;
 
 import javax.annotation.Resource;
 
+import com.qmth.themis.business.enums.*;
+import com.qmth.themis.business.service.*;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -19,19 +21,6 @@ import com.qmth.themis.business.entity.TBSession;
 import com.qmth.themis.business.entity.TMRocketMessage;
 import com.qmth.themis.business.entity.TOeExamBreakHistory;
 import com.qmth.themis.business.entity.TOeExamRecord;
-import com.qmth.themis.business.enums.BreakReasonEnum;
-import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.MqEnum;
-import com.qmth.themis.business.enums.SystemOperationEnum;
-import com.qmth.themis.business.enums.WebsocketStatusEnum;
-import com.qmth.themis.business.service.TBSessionService;
-import com.qmth.themis.business.service.TEExamStudentLogService;
-import com.qmth.themis.business.service.TEUserLogService;
-import com.qmth.themis.business.service.TMRocketMessageService;
-import com.qmth.themis.business.service.TOeExamBreakHistoryService;
-import com.qmth.themis.business.service.TOeExamRecordService;
-import com.qmth.themis.business.service.TOeFaceVerifyHistoryService;
-import com.qmth.themis.business.service.TOeLivenessVerifyHistoryService;
 import com.qmth.themis.business.templete.TaskExportTemplete;
 import com.qmth.themis.business.templete.TaskImportTemplete;
 import com.qmth.themis.business.templete.impl.TaskExamPaperImportTemplete;
@@ -83,11 +72,12 @@ public class MqLogicServiceImpl implements MqLogicService {
 
     @Resource
     TOeFaceVerifyHistoryService faceVerifyHistoryService;
-    
+
     @Resource
     TOeLivenessVerifyHistoryService livenessVerifyHistoryService;
-    
-    
+
+    @Resource
+    TEExamService teExamService;
 
     /**
      * mq最大重试次数逻辑
@@ -218,9 +208,9 @@ public class MqLogicServiceImpl implements MqLogicService {
             TOeExamRecord tOeExamRecord = tOeExamRecordService.getById(recordId);
             Integer breakCount = tOeExamRecord.getLeftBreakResumeCount();
             if (Objects.isNull(breakCount) || breakCount <= 0) {
-                //todo 没有断点次数,则强制交卷
                 tOeExamRecord.setStatus(ExamRecordStatusEnum.FINISHED);
                 tOeExamRecordService.updateById(tOeExamRecord);
+                teExamService.finish(tOeExamRecord.getExamStudentId(), tOeExamRecord.getId(), FinishTypeEnum.AUTO.name(), (int) (((System.currentTimeMillis() - tOeExamRecord.getClientLastSyncTime().getTime()) / 1000) + tOeExamRecord.getDurationSeconds()));
             } else {
                 breakCount--;
                 //增加断点记录
@@ -292,9 +282,9 @@ public class MqLogicServiceImpl implements MqLogicService {
         redisUtil.delete(key, mqDto.getId());
     }
 
-	@Override
-	public void execMqLivenessVerifySaveLogic(MqDto mqDto, String key) {
-		Gson gson = new Gson();
+    @Override
+    public void execMqLivenessVerifySaveLogic(MqDto mqDto, String key) {
+        Gson gson = new Gson();
         Map<String, Object> param = (Map<String, Object>) mqDto.getBody();
         Long id = (Long) param.get("id");
         Long recordId = (Long) param.get("recordId");
@@ -310,5 +300,5 @@ public class MqLogicServiceImpl implements MqLogicService {
         tmRocketMessage.setBody(JacksonUtil.parseJson(tmRocketMessage.getBody()));
         tmRocketMessageService.saveOrUpdate(tmRocketMessage);
         redisUtil.delete(key, mqDto.getId());
-	}
+    }
 }

+ 29 - 23
themis-task/src/main/java/com/qmth/themis/task/quartz/service/impl/QuartzLogicServiceImpl.java

@@ -8,9 +8,11 @@ import com.qmth.themis.business.enums.ExamRecordStatusEnum;
 import com.qmth.themis.business.enums.FinishTypeEnum;
 import com.qmth.themis.business.enums.MqEnum;
 import com.qmth.themis.business.service.TEExamActivityService;
+import com.qmth.themis.business.service.TEExamService;
 import com.qmth.themis.business.service.TEExamStudentService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.util.JacksonUtil;
+import com.qmth.themis.business.util.RedisUtil;
 import com.qmth.themis.mq.dto.MqDto;
 import com.qmth.themis.mq.enums.MqTagEnum;
 import com.qmth.themis.mq.enums.MqTopicEnum;
@@ -47,8 +49,11 @@ public class QuartzLogicServiceImpl implements QuartzLogicService {
     @Resource
     TOeExamRecordService tOeExamRecordService;
 
+//    @Resource
+//    MqDtoService mqDtoService;
+
     @Resource
-    MqDtoService mqDtoService;
+    TEExamService teExamService;
 
     /**
      * 考试场次quartz逻辑
@@ -77,32 +82,33 @@ public class QuartzLogicServiceImpl implements QuartzLogicService {
             tOeExamRecordList.forEach(s -> {
                 s.setStatus(ExamRecordStatusEnum.FINISHED);
                 s.setFinishTime(new Date());
-                s.setFinishType(FinishTypeEnum.INTERRUPT);
+                s.setFinishType(FinishTypeEnum.AUTO);
                 finalExamStudentIdList.add(s.getExamStudentId());
+                //加入交卷逻辑
+                teExamService.finish(s.getExamStudentId(), s.getId(), FinishTypeEnum.AUTO.name(), s.getDurationSeconds() + 60);
             });
             tOeExamRecordService.updateBatchById(tOeExamRecordList);
 
-            if (Objects.nonNull(examStudentIdList) && examStudentIdList.size() > 0) {
-                //获取该考试批次下所有考生,考试次数减1
-                QueryWrapper<TEExamStudent> teExamStudentQueryWrapper = new QueryWrapper<>();
-                teExamStudentQueryWrapper.lambda().in(TEExamStudent::getId, examStudentIdList);
-                List<TEExamStudent> teExamStudentList = teExamStudentService.list(teExamStudentQueryWrapper);
-                examStudentIdentityList = new ArrayList<>();
-                List<String> finalExamStudentIdentityList = examStudentIdentityList;
-                teExamStudentList.forEach(s -> {
-                    int count = Objects.isNull(s.getLeftExamCount()) ? 0 : s.getLeftExamCount();
-                    count--;
-                    s.setLeftExamCount(count < 0 ? 0 : count);
-                    finalExamStudentIdentityList.add(s.getIdentity());
-                });
-                //加入踢下线mq
-                teExamStudentService.updateBatchById(teExamStudentList);
-                MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.oe.name(), JacksonUtil.parseJson(finalExamStudentIdentityList), MqEnum.WEBSOCKET_OFFLINE_LOG, String.valueOf(teExamActivity.getId()), teExamActivity.getCode());
-                //发送强行离线mq start
-                mqDtoService.assembleSendOneWayMsg(mqDto);
-                //发送强行离线mq end
-            }
-            //todo 未完待续,需要加入交卷逻辑
+//            if (Objects.nonNull(examStudentIdList) && examStudentIdList.size() > 0) {
+//                //获取该考试批次下所有考生,考试次数减1
+//                QueryWrapper<TEExamStudent> teExamStudentQueryWrapper = new QueryWrapper<>();
+//                teExamStudentQueryWrapper.lambda().in(TEExamStudent::getId, examStudentIdList);
+//                List<TEExamStudent> teExamStudentList = teExamStudentService.list(teExamStudentQueryWrapper);
+//                examStudentIdentityList = new ArrayList<>();
+//                List<String> finalExamStudentIdentityList = examStudentIdentityList;
+//                teExamStudentList.forEach(s -> {
+//                    int count = Objects.isNull(s.getLeftExamCount()) ? 0 : s.getLeftExamCount();
+//                    count--;
+//                    s.setLeftExamCount(count < 0 ? 0 : count);
+//                    finalExamStudentIdentityList.add(s.getIdentity());
+//                });
+//                //加入踢下线mq
+//                teExamStudentService.updateBatchById(teExamStudentList);
+//                MqDto mqDto = new MqDto(MqTopicEnum.themisTopic.getCode(), MqTagEnum.oe.name(), JacksonUtil.parseJson(finalExamStudentIdentityList), MqEnum.WEBSOCKET_OFFLINE_LOG, String.valueOf(teExamActivity.getId()), teExamActivity.getCode());
+//                //发送强行离线mq start
+//                mqDtoService.assembleSendOneWayMsg(mqDto);
+//                //发送强行离线mq end
+//            }
         } else {
             log.info("考试场次:{}已删除", key);
         }