wangliang 3 rokov pred
rodič
commit
5925c053c8
18 zmenil súbory, kde vykonal 517 pridanie a 264 odobranie
  1. 9 25
      themis-admin/src/main/java/com/qmth/themis/admin/websocket/WebSocketAdminServer.java
  2. 54 0
      themis-admin/src/main/java/com/qmth/themis/admin/websocket/interceptor/WebSocketAdminHandshakeInterceptor.java
  3. 84 3
      themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java
  4. 11 4
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java
  5. 2 2
      themis-business/src/main/java/com/qmth/themis/business/util/AuthUtil.java
  6. 2 0
      themis-common/src/main/java/com/qmth/themis/common/contanst/Constants.java
  7. 27 23
      themis-common/src/main/java/com/qmth/themis/common/enums/ExceptionResultEnum.java
  8. 3 1
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java
  9. 15 0
      themis-exam/src/main/java/com/qmth/themis/exam/api/TEMobileController.java
  10. 15 15
      themis-exam/src/main/java/com/qmth/themis/exam/config/ExamConstant.java
  11. 4 2
      themis-exam/src/main/java/com/qmth/themis/exam/listener/service/MqOeLogicService.java
  12. 56 21
      themis-exam/src/main/java/com/qmth/themis/exam/listener/service/impl/MqOeLogicServiceImpl.java
  13. 81 72
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketMobileServer.java
  14. 29 26
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketOeServer.java
  15. 56 0
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/interceptor/WebSocketMobileHandshakeInterceptor.java
  16. 54 0
      themis-exam/src/main/java/com/qmth/themis/exam/websocket/interceptor/WebSocketOeHandshakeInterceptor.java
  17. 1 1
      themis-mq/src/main/java/com/qmth/themis/mq/service/MqLogicService.java
  18. 14 69
      themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

+ 9 - 25
themis-admin/src/main/java/com/qmth/themis/admin/websocket/WebSocketAdminServer.java

@@ -2,11 +2,13 @@ package com.qmth.themis.admin.websocket;
 
 import com.alibaba.fastjson.JSONObject;
 import com.google.gson.Gson;
+import com.qmth.themis.admin.websocket.interceptor.WebSocketAdminHandshakeInterceptor;
 import com.qmth.themis.admin.websocketTemplete.WebSocketAdminMessageTemplete;
 import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.WebsocketDto;
 import com.qmth.themis.business.entity.TBSession;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
 import com.qmth.themis.business.enums.WebsocketTypeEnum;
 import com.qmth.themis.business.util.*;
 import com.qmth.themis.common.contanst.Constants;
@@ -34,7 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @Author: wangliang
  * @Date: 2020/7/10
  */
-@ServerEndpoint("/ws/admin")
+@ServerEndpoint(value = "/ws/admin", configurator = WebSocketAdminHandshakeInterceptor.class)
 @Component
 public class WebSocketAdminServer
 //        implements Orderly
@@ -45,9 +47,8 @@ public class WebSocketAdminServer
      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
      */
     private Session session = null;
-    private Platform platform = null;
-    private String sessionId = null, ip = null, deviceId = null, authorization = null, url = "/ws/admin", websocketSessionId = null;
-    private Long time = null, userId = null, updateTime = null;
+    private String sessionId = null, ip = null, deviceId = null, websocketSessionId = null;
+    private Long userId = null, updateTime = null;
     private RedisUtil redisUtil;
     private Map<String, Object> tranMap = null;
 
@@ -56,27 +57,10 @@ public class WebSocketAdminServer
      */
     @OnOpen
     public void onOpen(Session session) {
-        Map<String, List<String>> mapParameter = session.getRequestParameterMap();
-        if (Objects.isNull(mapParameter)) {
-            throw new BusinessException(ExceptionResultEnum.PARAMS_ILLEGALITY);
-        }
-        log.info("mapParameter:{}", JacksonUtil.parseJson(mapParameter));
-        log.info("uri:{}", session.getRequestURI());
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
-        }
-        this.platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
-        }
-        this.deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
-        this.authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
-        this.time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
-        this.userId = Long.parseLong(mapParameter.get(Constants.HEADER_USER_ID).get(0));
-
-        redisUtil = SpringContextHolder.getBean(RedisUtil.class);
-        TBSession tbSession = AuthUtil.websocketAuthInterceptor(this.platform, this.deviceId, this.authorization, this.time, SystemConstant.GET, this.url);
-        this.session = session;
+        this.redisUtil = SpringContextHolder.getBean(RedisUtil.class);
+        this.userId = (Long) session.getUserProperties().get(Constants.HEADER_USER_ID);
+        this.deviceId = (String) session.getUserProperties().get(Constants.HEADER_DEVICE_ID);
+        TBSession tbSession = (TBSession) session.getUserProperties().get(Constants.HEADER_TB_SESSION);
         session.setMaxIdleTimeout(SystemConstant.WEBSOCKET_MAX_TIME_OUT);
         this.sessionId = tbSession.getId();
         websocketSessionId = String.valueOf(UidUtil.nextId());

+ 54 - 0
themis-admin/src/main/java/com/qmth/themis/admin/websocket/interceptor/WebSocketAdminHandshakeInterceptor.java

@@ -0,0 +1,54 @@
+package com.qmth.themis.admin.websocket.interceptor;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.entity.TBSession;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
+import com.qmth.themis.business.util.AuthUtil;
+import com.qmth.themis.common.contanst.Constants;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.enums.Platform;
+import com.qmth.themis.common.exception.BusinessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Description: websocket后台
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/10
+ */
+public class WebSocketAdminHandshakeInterceptor extends ServerEndpointConfig.Configurator {
+    private final static Logger log = LoggerFactory.getLogger(WebSocketAdminHandshakeInterceptor.class);
+
+    public static final String url = "/ws/admin";
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
+        Map<String, List<String>> mapParameter = request.getParameterMap();
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
+        }
+        Platform platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
+        }
+        String deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
+        String authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
+        Long time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
+        Long userId = Long.parseLong(mapParameter.get(Constants.HEADER_USER_ID).get(0));
+        MonitorVideoSourceEnum source = MonitorVideoSourceEnum.valueOf(mapParameter.get(Constants.HEADER_SOURCE).get(0));
+
+        TBSession tbSession = AuthUtil.websocketAuthInterceptor(platform, deviceId, authorization, time, SystemConstant.GET, url);
+        config.getUserProperties().put(Constants.HEADER_USER_ID, userId);
+        config.getUserProperties().put(Constants.HEADER_DEVICE_ID, deviceId);
+        config.getUserProperties().put(Constants.HEADER_TB_SESSION, tbSession);
+    }
+}

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

@@ -1,9 +1,13 @@
 package com.qmth.themis.business.constant;
 
+import com.google.common.collect.Sets;
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
 import com.qmth.themis.business.dto.ExpireTimeDTO;
-import com.qmth.themis.business.enums.ExamRecordStatusEnum;
-import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
+import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
+import com.qmth.themis.business.enums.*;
+import com.qmth.themis.business.service.CacheService;
+import com.qmth.themis.business.util.RedisUtil;
+import com.qmth.themis.business.util.SessionUtil;
 import com.qmth.themis.common.contanst.Constants;
 import com.qmth.themis.common.enums.ExceptionResultEnum;
 import com.qmth.themis.common.enums.Platform;
@@ -11,6 +15,7 @@ import com.qmth.themis.common.enums.Source;
 import com.qmth.themis.common.exception.BusinessException;
 
 import java.io.File;
+import java.security.NoSuchAlgorithmException;
 import java.util.*;
 
 /**
@@ -88,7 +93,7 @@ public class SystemConstant {
 
     public static final String EXTEND_COLUMN = "extendColumn";
 
-    public static final String MOBILE_REMOVE_WEBSOCKET = "mobileRemoveWebsocket";
+    public static final String REMOVE_WEBSOCKET = "removeWebsocket";
 
     public static final String IMPORT_INIT = "准备开始处理导入数据";
 
@@ -327,6 +332,8 @@ public class SystemConstant {
      */
     public static final String WEBSOCKET_OE_ONLINE_COUNT = "websocket:oe:online:count";
 
+    public static final String WEBSOCKET_MOBILE_ONLINE_COUNT = "websocket:mobile:online:count";
+
     public static final String WEBSOCKET_ADMIN_ONLINE_COUNT = "websocket:admin:online:count";
 
     public static final String GET = "get";
@@ -516,4 +523,78 @@ public class SystemConstant {
         }
         return videoSource.substring(0, videoSource.indexOf(monitorVideoSourceEnum.name().toLowerCase()) + monitorVideoSourceEnum.name().length());
     }
+
+    /**
+     * 客户端状态强制断掉
+     *
+     * @param recordId
+     */
+    public static void clientMonitorStatusStop(Long recordId) {
+        //更新客户端摄像头推流状态为stop
+        MonitorStatusSourceEnum cameraStatusSourceEnum = ExamRecordCacheUtil
+                .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA);
+        Long timestamp = System.currentTimeMillis();
+        if (Objects.nonNull(cameraStatusSourceEnum)) {
+            ExamRecordCacheUtil
+                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA, MonitorStatusSourceEnum.STOP,
+                            timestamp);
+        }
+        //更新客户端屏幕推流状态为stop
+        MonitorStatusSourceEnum screenStatusSourceEnum = ExamRecordCacheUtil
+                .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN);
+        if (Objects.nonNull(screenStatusSourceEnum)) {
+            ExamRecordCacheUtil
+                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN, MonitorStatusSourceEnum.STOP,
+                            timestamp);
+        }
+    }
+
+    /**
+     * 移动端状态强制断掉
+     *
+     * @param studentId
+     * @param recordId
+     * @param deleteSession
+     * @throws NoSuchAlgorithmException
+     */
+    public static void mobileMonitorStatusStop(Long studentId, Long recordId, boolean deleteSession) throws NoSuchAlgorithmException {
+        CacheService cacheService = SpringContextHolder.getBean(CacheService.class);
+        RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
+        Long timestamp = System.currentTimeMillis();
+
+        TEStudentCacheDto teStudent = cacheService.addStudentAccountCache(studentId);
+        Optional.ofNullable(teStudent).orElseThrow(() -> new BusinessException("学生数据为空"));
+
+        //更新移动端第一机位推流状态为stop
+        MonitorStatusSourceEnum mobileFirstStatusSourceEnum = ExamRecordCacheUtil
+                .getMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_FIRST);
+        if (Objects.nonNull(mobileFirstStatusSourceEnum)) {
+            ExamRecordCacheUtil
+                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_FIRST, MonitorStatusSourceEnum.STOP,
+                            timestamp);
+            ExamRecordCacheUtil.setMobileFirstWebsocketStatus(recordId, WebsocketStatusEnum.OFF_LINE, timestamp);
+            //是否删除session
+            if (deleteSession) {
+                String sessionId = SessionUtil.digest(teStudent.getId(),
+                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_FIRST.name());
+                redisUtil.deleteUserSession(sessionId);
+            }
+        }
+
+        //更新移动端第二机位推流状态为stop
+        MonitorStatusSourceEnum mobileSecondStatusSourceEnum = ExamRecordCacheUtil
+                .getMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_SECOND);
+        if (Objects.nonNull(mobileSecondStatusSourceEnum)) {
+            ExamRecordCacheUtil
+                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_SECOND, MonitorStatusSourceEnum.STOP,
+                            timestamp);
+            ExamRecordCacheUtil.setMobileSecondWebsocketStatus(recordId, WebsocketStatusEnum.OFF_LINE, timestamp);
+            //是否删除session
+            if (deleteSession) {
+                String sessionId = SessionUtil.digest(teStudent.getId(),
+                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_SECOND.name());
+                redisUtil.deleteUserSession(sessionId);
+            }
+        }
+    }
 }

+ 11 - 4
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -416,7 +416,6 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             }
         } else {
             Long start = ac.getStartTime();
-            Long end = ac.getStartTime() + (ac.getOpeningSeconds() * 1000);
             if (now.getTime() < start) {
                 throw new BusinessException(ExceptionResultEnum.NOT_ALLOW_MAX_START_TIME);
             }
@@ -424,11 +423,12 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 //集中不启用统一收卷,考试快结束时,断点,再登录时, 时间已经超过考试截止时间,断点时,此时应该能登录继续作答
                 Integer forceFinish = ExamRecordCacheUtil.getForceFinish(recordId);
                 if (forceFinish != null && forceFinish.intValue() == 1) {
-                    if (now.getTime() > end) {
-                        throw new BusinessException(ExceptionResultEnum.MAX_START_TIME_FINISHED);
+                    if (now.getTime() > ac.getFinishTime()) {
+                        throw new BusinessException(ExceptionResultEnum.FORCE_FINISH);
                     }
                 }
             } else {
+                Long end = ac.getStartTime() + (ac.getOpeningSeconds() * 1000);
                 if (now.getTime() > end) {
                     throw new BusinessException(ExceptionResultEnum.MAX_START_TIME_FINISHED);
                 }
@@ -1177,8 +1177,15 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         TEStudentCacheDto teStudentCacheDto = cacheService.addStudentAccountCache(es.getStudentId());
         ExamingDataCacheUtil.deleteUnFinishedRecordId(studentId);
 
-        MqDto mobileMqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_STOP.name(), recordId, MqTagEnum.EXAM_STOP, String.valueOf(recordId), Collections.singletonMap(SystemConstant.RECORD_ID, recordId), String.valueOf(recordId));
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(SystemConstant.REMOVE_WEBSOCKET, true);
+
+        MqDto clientMqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.OE_WEBSOCKET_EXAM_STOP.name(), recordId, MqTagEnum.OE_WEBSOCKET_EXAM_STOP, String.valueOf(recordId), properties, String.valueOf(recordId));
+        mqDtoService.assembleSendOneOrderMsg(clientMqDto);
+
+        MqDto mobileMqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_STOP.name(), recordId, MqTagEnum.EXAM_STOP, String.valueOf(recordId), properties, String.valueOf(recordId));
         mqDtoService.assembleSendOneOrderMsg(mobileMqDto);
+
         //异步持久化
         checkToPersisted(recordId);
         //mq发送消息start

+ 2 - 2
themis-business/src/main/java/com/qmth/themis/business/util/AuthUtil.java

@@ -148,11 +148,11 @@ public class AuthUtil {
         TBSession tbSession = (TBSession) redisUtil.getUserSession(sessionId);
         if (Objects.isNull(tbSession)) {
             log.warn("Authorization faile: session id not exists: " + sessionId);
-            throw new BusinessException(ExceptionResultEnum.LOGIN_NO);
+            throw new BusinessException(ExceptionResultEnum.NO_SESSION);
         }
         if (tbSession.getExpireTime() <= System.currentTimeMillis() || info.getTimestamp() > tbSession.getExpireTime()) {
             log.warn("Authorization faile: session has expired, expire time=" + tbSession.getExpireTime());
-            throw new BusinessException(ExceptionResultEnum.LOGIN_NO);
+            throw new BusinessException(ExceptionResultEnum.TOKEN_INVALID);
         }
         if (!info.validate(tbSession.getAccessToken())) {
             log.warn("Authorization faile: access token invalid, session token is " + tbSession.getAccessToken());

+ 2 - 0
themis-common/src/main/java/com/qmth/themis/common/contanst/Constants.java

@@ -30,6 +30,8 @@ public interface Constants {
 
     public static final String HEADER_USER_ID = "userId";
 
+    public static final String HEADER_TB_SESSION = "tbSession";
+
     /**
      * aes相关
      */

+ 27 - 23
themis-common/src/main/java/com/qmth/themis/common/enums/ExceptionResultEnum.java

@@ -11,51 +11,53 @@ public enum ExceptionResultEnum {
     /**
      * 考生端错误
      */
-    NOT_FOUND_EXAM_RECORD(500,500020,"未找到考试记录"),
+    NOT_FOUND_EXAM_RECORD(500, 500020, "未找到考试记录"),
 
-    NOT_FOUND_EXAM_STUDENT(500,500021,"未找到考生"),
+    NOT_FOUND_EXAM_STUDENT(500, 500021, "未找到考生"),
 
 //    EXAM_ALREADY_FINISHED(500,500022,"该考试已结束"),
 
-    EXAM_ID_NOT_EQUALY(500,500023,"考试记录的学生Id和当前登录用户不一致"),
+    EXAM_ID_NOT_EQUALY(500, 500023, "考试记录的学生Id和当前登录用户不一致"),
 
-    QR_CODE_EXPIRE(500,500024,"二维码已过期"),
+    QR_CODE_EXPIRE(500, 500024, "二维码已过期"),
 
-    ORG_ID_OR_EXAM_ID_NOT_CHOOSE(500,500025,"机构id或考试id必须有一个"),
+    ORG_ID_OR_EXAM_ID_NOT_CHOOSE(500, 500025, "机构id或考试id必须有一个"),
 
-    STUDENT_NOT_ALLOW_LOGIN(500,500026,"该学生正在考试,不能登录"),
+    STUDENT_NOT_ALLOW_LOGIN(500, 500026, "该学生正在考试,不能登录"),
 
 //    EXAM_STATUS_NOT_NULL(500,500027,"考试状态不能为空"),
 
-    EXAM_STATUS_NOT_ALLOW_IP(500,500028,"考生IP不被允许"),
+    EXAM_STATUS_NOT_ALLOW_IP(500, 500028, "考生IP不被允许"),
 
-    EXAM_LEFT_COUNT_IS_ZERO(500,500029,"没有剩余考试次数"),
+    EXAM_LEFT_COUNT_IS_ZERO(500, 500029, "没有剩余考试次数"),
 
-    NOT_FOUND_EXAM_ACTIVITY(500,500030,"未找到考试场次"),
+    NOT_FOUND_EXAM_ACTIVITY(500, 500030, "未找到考试场次"),
 
-    NOT_ALLOW_MAX_START_TIME(500,500031,"没有到允许开考的时间"),
+    NOT_ALLOW_MAX_START_TIME(500, 500031, "没有到允许开考的时间"),
 
-    MAX_START_TIME_FINISHED(500,500032,"允许开考的时间已结束"),
+    MAX_START_TIME_FINISHED(500, 500032, "允许开考的时间已结束"),
 
-    NOT_FOUND_EXAM_COURSE(500,500033,"未找到考试科目"),
+    NOT_FOUND_EXAM_COURSE(500, 500033, "未找到考试科目"),
 
-    EXAM_COURSE_OR_EXAM_PAPER_NOT_BIND(500,500034,"考试科目未绑定试卷"),
+    EXAM_COURSE_OR_EXAM_PAPER_NOT_BIND(500, 500034, "考试科目未绑定试卷"),
 
-    NOT_FOUND_EXAM_PAPER(500,500035,"未找到试卷"),
+    NOT_FOUND_EXAM_PAPER(500, 500035, "未找到试卷"),
 
-    CLIENT_NET_OFFLINE(500,500036,"客户端网络离线"),
+    CLIENT_NET_OFFLINE(500, 500036, "客户端网络离线"),
 
-    CLIENT_CAMERA_OFFLINE(500,500037,"客户端摄像头离线"),
+    CLIENT_CAMERA_OFFLINE(500, 500037, "客户端摄像头离线"),
 
-    CLIENT_SCREEN_OFFLINE(500,500038,"客户端屏幕离线"),
+    CLIENT_SCREEN_OFFLINE(500, 500038, "客户端屏幕离线"),
 
-    MOBILE_FIRST_OFFLINE(500,500039,"移动端第一机位离线"),
+    MOBILE_FIRST_OFFLINE(500, 500039, "移动端第一机位离线"),
 
-    MOBILE_SECOND_OFFLINE(500,500040,"移动端第二机位离线"),
+    MOBILE_SECOND_OFFLINE(500, 500040, "移动端第二机位离线"),
 
-    NOT_FOUND_PAPER_OBJECTIVE_ANSWER(500,500041,"未找到试卷客观题答案"),
+    NOT_FOUND_PAPER_OBJECTIVE_ANSWER(500, 500041, "未找到试卷客观题答案"),
 
-    NOT_FOUND_QUESTION_OBJECTIVE_ANSWER(500,500042,"未找到小题客观题答案"),
+    NOT_FOUND_QUESTION_OBJECTIVE_ANSWER(500, 500042, "未找到小题客观题答案"),
+
+    FORCE_FINISH(500, 500043, "已超过集中统一收卷时间"),
 
     /**
      * 系统预置
@@ -204,6 +206,8 @@ public enum ExceptionResultEnum {
 
     RECORD_ID_INVALID(401, 401010, "recordId无效"),
 
+    NO_SESSION(401, 401011, "请先登录"),
+
     /**
      * 404
      */
@@ -245,7 +249,7 @@ public enum ExceptionResultEnum {
 
     REQUEST_AWAIT(500, 500016, "请求等待:请稍后重试"),
 
-    ATTACHMENT_ERROR(500, 500017,"上传文件失败"),
+    ATTACHMENT_ERROR(500, 500017, "上传文件失败"),
 
     FINISH_TYPE_ERROR(500, 500018, "考试结束类型错误"),
 
@@ -265,7 +269,7 @@ public enum ExceptionResultEnum {
     private int code;
     private String message;
 
-    ExceptionResultEnum(int code,int statusCode, String message) {
+    ExceptionResultEnum(int code, int statusCode, String message) {
         this.code = code;
         this.statusCode = statusCode;
         this.message = message;

+ 3 - 1
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -37,6 +37,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -124,7 +125,7 @@ public class TEExamController {
     @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
     public Result exit(@ApiJsonObject(name = "saveInvigilateUser", value = {
             @ApiJsonProperty(key = "recordId", type = "long", example = "1", description = "考试记录id", required = true)
-    }) @ApiParam(value = "考试记录id", required = true) @RequestBody Map<String, Object> mapParameter) {
+    }) @ApiParam(value = "考试记录id", required = true) @RequestBody Map<String, Object> mapParameter) throws NoSuchAlgorithmException {
         if (Objects.isNull(mapParameter.get("recordId")) || Objects.equals(mapParameter.get("recordId"), "")) {
             throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
         }
@@ -228,6 +229,7 @@ public class TEExamController {
         if (param.getVersion() == null) {
             throw new BusinessException("时间戳不能为空");
         }
+
         AnswerSubmitBean ret = teExamService.answerSubmit(teStudent.getId(), param.getRecordId(), param.getMainNumber(),
                 param.getSubNumber(), param.getSubIndex(), param.getAnswer(), param.getVersion(),
                 param.getDurationSeconds());

+ 15 - 0
themis-exam/src/main/java/com/qmth/themis/exam/api/TEMobileController.java

@@ -173,4 +173,19 @@ public class TEMobileController {
         mqDtoService.assembleSendOneOrderMsg(mqDto);
         return ResultUtil.ok(ret);
     }
+
+    @ApiOperation(value = "查询考试状态")
+    @RequestMapping(value = "/exam/status", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    public Result examStatus(@RequestBody MobileAnswerSubmitParamBean param) {
+        if (Objects.isNull(param)) {
+            throw new BusinessException(ExceptionResultEnum.PARAMS_ILLEGALITY);
+        }
+        if (Objects.isNull(param.getRecordId())) {
+            throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put("status", ExamRecordCacheUtil.getStatus(param.getRecordId()));
+        return ResultUtil.ok(map);
+    }
 }

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

@@ -1,6 +1,7 @@
 package com.qmth.themis.exam.config;
 
 import com.qmth.themis.business.cache.ExamRecordCacheUtil;
+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;
@@ -8,9 +9,12 @@ import com.qmth.themis.business.enums.MonitorStatusSourceEnum;
 import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
 import com.qmth.themis.business.enums.MqTagEnum;
 import com.qmth.themis.business.service.MqDtoService;
+import com.qmth.themis.business.service.TEExamStudentService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.util.MqUtil;
+import com.qmth.themis.common.exception.BusinessException;
 
+import java.security.NoSuchAlgorithmException;
 import java.util.*;
 
 /**
@@ -28,33 +32,29 @@ public class ExamConstant {
      * @param recordId
      * @param clientStop
      */
-    public static void sendExamStopMsg(Long recordId, boolean clientStop, boolean mobileStop) {
+    public static void sendExamStopMsg(Long recordId, boolean clientStop, boolean mobileStop) throws NoSuchAlgorithmException {
         MqUtil mqUtil = SpringContextHolder.getBean(MqUtil.class);
         MqDtoService mqDtoService = SpringContextHolder.getBean(MqDtoService.class);
-        Map mapParameter = new HashMap<>();
+        TEExamStudentService teExamStudentService = SpringContextHolder.getBean(TEExamStudentService.class);
+        Map<String, Object> mapParameter = new HashMap<>();
         mapParameter.put(SystemConstant.RECORD_ID, recordId);
+        mapParameter.put(SystemConstant.REMOVE_WEBSOCKET, true);
         //客户端考试结束
         if (clientStop) {
             MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.OE_WEBSOCKET_EXAM_STOP.name(), recordId, MqTagEnum.OE_WEBSOCKET_EXAM_STOP, String.valueOf(recordId), mapParameter, String.valueOf(recordId));
             mqDtoService.assembleSendOneOrderMsg(mqDto);
         } else {//结束推流状态
-            String monitorVideoSource = ExamRecordCacheUtil.getMonitorVideoSource(recordId);
-            List<String> monitorVideoSourceList = null;
-            if (Objects.nonNull(monitorVideoSource) && !Objects.equals(monitorVideoSource.trim().replaceAll(" ", ""), "")) {
-                monitorVideoSourceList = Arrays.asList(monitorVideoSource.trim().toUpperCase().replaceAll(" ", "").split(","));
-            }
-            Long timestamp = System.currentTimeMillis();
-            if (Objects.nonNull(monitorVideoSourceList) && monitorVideoSourceList.contains(MonitorVideoSourceEnum.CLIENT_CAMERA.name())) {
-                ExamRecordCacheUtil.setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA, MonitorStatusSourceEnum.STOP, timestamp);
-            }
-            if (Objects.nonNull(monitorVideoSourceList) && monitorVideoSourceList.contains(MonitorVideoSourceEnum.CLIENT_SCREEN.name())) {
-                ExamRecordCacheUtil.setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN, MonitorStatusSourceEnum.STOP, timestamp);
-            }
+            SystemConstant.clientMonitorStatusStop(recordId);
             TOeExamRecordService toeExamRecordService = SpringContextHolder.getBean(TOeExamRecordService.class);
-            toeExamRecordService.sendExamRecordDataSaveMq(recordId, timestamp);
+            toeExamRecordService.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
         }
         //移动端考试结束
         if (mobileStop) {
+            Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
+            ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
+            Optional.ofNullable(examStudentCacheBean).orElseThrow(() -> new BusinessException("考生数据为空"));
+
+            SystemConstant.mobileMonitorStatusStop(examStudentId, recordId, false);
             MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_STOP.name(), recordId, MqTagEnum.EXAM_STOP, String.valueOf(recordId), mapParameter, String.valueOf(recordId));
             mqDtoService.assembleSendOneOrderMsg(mqDto);
         }

+ 4 - 2
themis-exam/src/main/java/com/qmth/themis/exam/listener/service/MqOeLogicService.java

@@ -2,6 +2,8 @@ package com.qmth.themis.exam.listener.service;
 
 import com.qmth.themis.business.dto.MqDto;
 
+import java.security.NoSuchAlgorithmException;
+
 /**
  * @Description: mq执行逻辑
  * @Param:
@@ -17,7 +19,7 @@ public interface MqOeLogicService {
      * @param mqDto
      * @param key
      */
-    public void execMqOeLogic(MqDto mqDto, String key);
+    public void execMqOeLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException;
 
     /**
      * oe mobile 逻辑
@@ -25,5 +27,5 @@ public interface MqOeLogicService {
      * @param mqDto
      * @param key
      */
-    public void execMqOeMobileLogic(MqDto mqDto, String key);
+    public void execMqOeMobileLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException;
 }

+ 56 - 21
themis-exam/src/main/java/com/qmth/themis/exam/listener/service/impl/MqOeLogicServiceImpl.java

@@ -12,12 +12,10 @@ import com.qmth.themis.business.entity.TEExamStudentLog;
 import com.qmth.themis.business.entity.TIeExamInvigilateNotice;
 import com.qmth.themis.business.entity.TMRocketMessage;
 import com.qmth.themis.business.enums.*;
-import com.qmth.themis.business.service.TEExamStudentLogService;
-import com.qmth.themis.business.service.TEExamStudentService;
-import com.qmth.themis.business.service.TIeExamInvigilateNoticeService;
-import com.qmth.themis.business.service.TMRocketMessageService;
+import com.qmth.themis.business.service.*;
 import com.qmth.themis.business.util.JacksonUtil;
 import com.qmth.themis.business.util.RedisUtil;
+import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.exam.listener.service.MqOeLogicService;
 import com.qmth.themis.exam.websocket.WebSocketMobileServer;
 import com.qmth.themis.exam.websocket.WebSocketOeServer;
@@ -27,6 +25,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.security.NoSuchAlgorithmException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -56,6 +55,9 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
     @Resource
     TIeExamInvigilateNoticeService tIeExamInvigilateNoticeService;
 
+    @Resource
+    CacheService cacheService;
+
     /**
      * oe逻辑
      *
@@ -64,7 +66,7 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
      */
     @Override
     @Transactional
-    public void execMqOeLogic(MqDto mqDto, String key) {
+    public void execMqOeLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException {
         Gson gson = new Gson();
         ConcurrentHashMap<String, WebSocketOeServer> webSocketMap = WebSocketOeServer.getWebSocketMap();
         String tag = mqDto.getTag();
@@ -203,6 +205,14 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
                         case "OE_WEBSOCKET_EXAM_STOP":
                             if (!Objects.equals(ExamRecordStatusEnum.ANSWERING, examRecordStatusEnum)) {
                                 websocketDto = new WebsocketDto(WebsocketTypeEnum.EXAM_STOP.name(), prop);
+                                SystemConstant.clientMonitorStatusStop(recordId);
+                                Map<String, Object> properties = mqDto.getProperties();
+                                if (Objects.nonNull(properties) && properties.size() > 0) {
+                                    Boolean removeWebsocket = (Boolean) properties.get(SystemConstant.REMOVE_WEBSOCKET);
+                                    if (Objects.nonNull(removeWebsocket) && removeWebsocket) {
+                                        WebSocketOeServer.close(webSocketOeServer);
+                                    }
+                                }
                             }
                             break;
                         case "MONITOR_START":
@@ -221,6 +231,26 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
                                     && Objects.nonNull(monitorStatusBean.getTimestamp())
                                     && mqDto.getTimestamp().longValue() >= monitorStatusBean.getTimestamp().longValue()) {
                                 websocketDto = new WebsocketDto(WebsocketTypeEnum.MOBILE_MONITOR_STOP.name(), prop);
+                                ConcurrentHashMap<String, WebSocketMobileServer> webSocketMobileMap = WebSocketMobileServer.getWebSocketMap();
+
+                                String mobileWebsocketId = null;
+                                WebsocketStatusEnum websocketStatusEnum = null;
+                                if (source == MonitorVideoSourceEnum.MOBILE_FIRST) {
+                                    mobileWebsocketId = ExamRecordCacheUtil.getMobileFirstWebsocketId(recordId);
+                                    websocketStatusEnum = ExamRecordCacheUtil.getMobileFirstWebsocketStatus(recordId);
+                                } else if (source == MonitorVideoSourceEnum.MOBILE_SECOND) {
+                                    mobileWebsocketId = ExamRecordCacheUtil.getMobileSecondWebsocketId(recordId);
+                                    websocketStatusEnum = ExamRecordCacheUtil.getMobileSecondWebsocketStatus(recordId);
+                                }
+                                if (Objects.nonNull(websocketStatusEnum) && websocketStatusEnum == WebsocketStatusEnum.ON_LINE) {
+                                    WebSocketMobileServer webSocketMobileServer = webSocketMobileMap.get(mobileWebsocketId + "-" + source.name());
+                                    Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
+                                    ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
+                                    Optional.ofNullable(examStudentCacheBean).orElseThrow(() -> new BusinessException("考生数据为空"));
+
+                                    SystemConstant.mobileMonitorStatusStop(examStudentCacheBean.getStudentId(), recordId, true);
+                                    WebSocketMobileServer.close(webSocketMobileServer);
+                                }
                             }
                             break;
                         case "OE_WEBSOCKET_MOBILE_MONITOR_STATUS":
@@ -261,30 +291,35 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
      */
     @Override
     @Transactional
-    public void execMqOeMobileLogic(MqDto mqDto, String key) {
+    public void execMqOeMobileLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException {
         Gson gson = new Gson();
         String tag = mqDto.getTag();
         ConcurrentHashMap<String, WebSocketMobileServer> webSocketMap = WebSocketMobileServer.getWebSocketMap();
         if (Objects.equals(MqTagEnum.EXAM_STOP.name(), tag)//考试退出
                 || Objects.equals(MqTagEnum.EXAM_START.name(), tag)) {//考试开始
             Long recordId = Long.parseLong(String.valueOf(mqDto.getBody()));
+            ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+            Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
+            ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
+            Optional.ofNullable(examStudentCacheBean).orElseThrow(() -> new BusinessException("考生数据为空"));
+
             String mobileFirstWebsocketId = ExamRecordCacheUtil.getMobileFirstWebsocketId(recordId);
             if (Objects.nonNull(mobileFirstWebsocketId) && Objects.nonNull(webSocketMap.get(mobileFirstWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name()))) {
-                WebSocketMobileServer webSocketMobileServer = webSocketMap.get(mobileFirstWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name());
-                ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                WebSocketMobileServer webSocketFirstMobileServer = webSocketMap.get(mobileFirstWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_FIRST.name());
                 if (Objects.nonNull(examRecordStatusEnum)
-                        && Objects.nonNull(webSocketMobileServer.getRecordId())
-                        && webSocketMobileServer.getRecordId().longValue() == recordId.longValue()) {
+                        && Objects.nonNull(webSocketFirstMobileServer.getRecordId())
+                        && webSocketFirstMobileServer.getRecordId().longValue() == recordId.longValue()) {
                     WebsocketDto websocketDto = null;
                     switch (tag.toUpperCase()) {
                         case "EXAM_STOP":
                             if (!Objects.equals(ExamRecordStatusEnum.ANSWERING, examRecordStatusEnum)) {
                                 websocketDto = new WebsocketDto(WebsocketTypeEnum.EXAM_STOP.name(), mqDto.getProperties());
+                                SystemConstant.mobileMonitorStatusStop(examStudentCacheBean.getStudentId(), recordId, false);
                                 Map<String, Object> properties = mqDto.getProperties();
                                 if (Objects.nonNull(properties) && properties.size() > 0) {
-                                    Boolean mobileRemoveWebsocket = (Boolean) properties.get(SystemConstant.MOBILE_REMOVE_WEBSOCKET);
-                                    if (Objects.nonNull(mobileRemoveWebsocket) && mobileRemoveWebsocket) {
-                                        webSocketMap.remove(mobileFirstWebsocketId);
+                                    Boolean removeWebsocket = (Boolean) properties.get(SystemConstant.REMOVE_WEBSOCKET);
+                                    if (Objects.nonNull(removeWebsocket) && removeWebsocket) {
+                                        WebSocketMobileServer.close(webSocketFirstMobileServer);
                                     }
                                 }
                             }
@@ -298,26 +333,26 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
                         default:
                             break;
                     }
-                    webSocketMobileServer.sendMessage(websocketDto);
+                    webSocketFirstMobileServer.sendMessage(websocketDto);
                 }
             }
             String mobileSecondWebsocketId = ExamRecordCacheUtil.getMobileSecondWebsocketId(recordId);
             if (Objects.nonNull(mobileSecondWebsocketId) && Objects.nonNull(webSocketMap.get(mobileSecondWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name()))) {
-                WebSocketMobileServer webSocketMobileServer = webSocketMap.get(mobileSecondWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name());
-                ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
+                WebSocketMobileServer webSocketSecondMobileServer = webSocketMap.get(mobileSecondWebsocketId + "-" + MonitorVideoSourceEnum.MOBILE_SECOND.name());
                 if (Objects.nonNull(examRecordStatusEnum)
-                        && Objects.nonNull(webSocketMobileServer.getRecordId())
-                        && webSocketMobileServer.getRecordId().longValue() == recordId.longValue()) {
+                        && Objects.nonNull(webSocketSecondMobileServer.getRecordId())
+                        && webSocketSecondMobileServer.getRecordId().longValue() == recordId.longValue()) {
                     WebsocketDto websocketDto = null;
                     switch (tag.toUpperCase()) {
                         case "EXAM_STOP":
                             if (!Objects.equals(ExamRecordStatusEnum.ANSWERING, examRecordStatusEnum)) {
                                 websocketDto = new WebsocketDto(WebsocketTypeEnum.EXAM_STOP.name(), mqDto.getProperties());
+                                SystemConstant.mobileMonitorStatusStop(examStudentCacheBean.getStudentId(), recordId, false);
                                 Map<String, Object> properties = mqDto.getProperties();
                                 if (Objects.nonNull(properties) && properties.size() > 0) {
-                                    Boolean mobileRemoveWebsocket = (Boolean) properties.get(SystemConstant.MOBILE_REMOVE_WEBSOCKET);
+                                    Boolean mobileRemoveWebsocket = (Boolean) properties.get(SystemConstant.REMOVE_WEBSOCKET);
                                     if (Objects.nonNull(mobileRemoveWebsocket) && mobileRemoveWebsocket) {
-                                        webSocketMap.remove(mobileSecondWebsocketId);
+                                        WebSocketMobileServer.close(webSocketSecondMobileServer);
                                     }
                                 }
                             }
@@ -331,7 +366,7 @@ public class MqOeLogicServiceImpl implements MqOeLogicService {
                         default:
                             break;
                     }
-                    webSocketMobileServer.sendMessage(websocketDto);
+                    webSocketSecondMobileServer.sendMessage(websocketDto);
                 }
             }
         }

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

@@ -13,10 +13,9 @@ import com.qmth.themis.business.service.MqDtoService;
 import com.qmth.themis.business.service.TOeExamRecordService;
 import com.qmth.themis.business.util.*;
 import com.qmth.themis.common.contanst.Constants;
-import com.qmth.themis.common.enums.ExceptionResultEnum;
-import com.qmth.themis.common.enums.Platform;
 import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.exam.listener.service.MqOeLogicService;
+import com.qmth.themis.exam.websocket.interceptor.WebSocketMobileHandshakeInterceptor;
 import com.qmth.themis.exam.websocketTemplete.WebSocketMobileMessageTemplete;
 import com.qmth.themis.mq.templete.Concurrently;
 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
@@ -44,7 +43,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @Author: wangliang
  * @Date: 2020/7/10
  */
-@ServerEndpoint("/ws/mobile")
+@ServerEndpoint(value = "/ws/mobile", configurator = WebSocketMobileHandshakeInterceptor.class)
 @Component
 public class WebSocketMobileServer implements Concurrently {
     private final static Logger log = LoggerFactory.getLogger(WebSocketMobileServer.class);
@@ -53,9 +52,8 @@ public class WebSocketMobileServer implements Concurrently {
      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
      */
     private Session session = null;
-    private Platform platform = null;
-    private String sessionId = null, ip = null, deviceId = null, authorization = null, url = "/ws/mobile", websocketSessionId = null;
-    private Long time = null, recordId = null, updateTime = null;
+    private String sessionId = null, ip = null, deviceId = null, websocketSessionId = null;
+    private Long recordId = null, updateTime = null;
     private RedisUtil redisUtil;
     private Map<String, Object> tranMap = null;
     private MonitorVideoSourceEnum source = null;
@@ -65,28 +63,11 @@ public class WebSocketMobileServer implements Concurrently {
      */
     @OnOpen
     public void onOpen(Session session) {
-        Map<String, List<String>> mapParameter = session.getRequestParameterMap();
-        if (Objects.isNull(mapParameter)) {
-            throw new BusinessException(ExceptionResultEnum.PARAMS_ILLEGALITY);
-        }
-        log.info("mapParameter:{}", JacksonUtil.parseJson(mapParameter));
-        log.info("uri:{}", session.getRequestURI());
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
-        }
-        this.platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
-        }
-        this.deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
-        this.authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
-        this.time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
-        this.recordId = Long.parseLong(mapParameter.get(Constants.HEADER_RECORD_ID).get(0));
-        this.source = MonitorVideoSourceEnum.valueOf(mapParameter.get(Constants.HEADER_SOURCE).get(0));
-
         this.redisUtil = SpringContextHolder.getBean(RedisUtil.class);
-        TBSession tbSession = AuthUtil.websocketAuthInterceptor(this.platform, this.deviceId, this.authorization, this.time, SystemConstant.GET, this.url);
-        SystemConstant.checkExamStatus(this.recordId);
+        this.recordId = (Long) session.getUserProperties().get(Constants.HEADER_RECORD_ID);
+        this.deviceId = (String) session.getUserProperties().get(Constants.HEADER_DEVICE_ID);
+        TBSession tbSession = (TBSession) session.getUserProperties().get(Constants.HEADER_TB_SESSION);
+        this.source = (MonitorVideoSourceEnum) session.getUserProperties().get(Constants.HEADER_SOURCE);
         this.session = session;
         session.setMaxIdleTimeout(SystemConstant.WEBSOCKET_MAX_TIME_OUT);
         this.sessionId = tbSession.getId();
@@ -96,10 +77,9 @@ public class WebSocketMobileServer implements Concurrently {
             webSocketMap.put(this.websocketSessionId + "-" + this.source.name(), this);
         } else {
             webSocketMap.put(this.websocketSessionId + "-" + this.source.name(), this);
-//            addOnlineCount();
+            addOnlineCount();
         }
-//        log.info("用户连接:{},当前在线人数为:{}", this.sessionId, getOnlineCount());
-        log.info("用户连接:{}", this.websocketSessionId);
+        log.info("用户连接:{},当前在线人数为:{}", this.websocketSessionId, getOnlineCount());
         InetSocketAddress addr = (InetSocketAddress) WebsocketUtil.getFieldInstance(this.session.getAsyncRemote(), "base#socketWrapper#socket#sc#remoteAddress");
         this.ip = addr.toString().replace("/", "").split(":")[0];
         log.info("ip[:{}]连接成功", this.ip);
@@ -121,7 +101,7 @@ public class WebSocketMobileServer implements Concurrently {
         if (webSocketMap.containsKey(this.websocketSessionId + "-" + this.source.name())) {
             webSocketMap.remove(this.websocketSessionId + "-" + this.source.name());
             //从set中删除
-//            subOnlineCount();
+            subOnlineCount();
             //判断是否是正常退出
             Long timestamp = System.currentTimeMillis();
             ExamRecordCacheUtil.setMonitorStatus(this.recordId, this.source, MonitorStatusSourceEnum.STOP, timestamp);
@@ -141,8 +121,7 @@ public class WebSocketMobileServer implements Concurrently {
             MqDto mqDtoStop = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.MONITOR_STOP.name(), recordId, MqTagEnum.MONITOR_STOP, String.valueOf(recordId), mqMap, String.valueOf(recordId));
             mqDtoService.assembleSendOneOrderMsg(mqDtoStop);
         }
-//        log.info("用户退出:{},当前在线人数为:{},updateTime:{}", this.sessionId, getOnlineCount(), this.updateTime);
-        log.info("用户退出:{},updateTime:{}", this.websocketSessionId, this.updateTime);
+        log.info("用户退出:{},当前在线人数为:{},updateTime:{}", this.sessionId, getOnlineCount(), this.updateTime);
     }
 
     /**
@@ -200,50 +179,80 @@ public class WebSocketMobileServer implements Concurrently {
         this.updateTime = System.currentTimeMillis();
     }
 
-//    /**
-//     * 获取在线人数
-//     *
-//     * @return
-//     */
-//    public synchronized int getOnlineCount() {
-//        Object o = redisUtil.get(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT);
-//        return Objects.isNull(o) ? 0 : (int) o;
-//    }
-//
-//    /**
-//     * 在线人数加一
-//     */
-//    public synchronized void addOnlineCount() {
-//        if (redisUtil.lock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.sessionId, SystemConstant.REDIS_LOCK_WEBSOCKET_TIME_OUT)) {
-//            Object o = redisUtil.get(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT);
-//            int count = 0;
-//            if (Objects.nonNull(o)) {
-//                count = (int) o;
-//            }
-//            count++;
-//            redisUtil.set(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT, count);
-//        }
-//    }
-//
-//    /**
-//     * 在线人数减一
-//     */
-//    public synchronized void subOnlineCount() {
-//        if (redisUtil.lock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.sessionId, SystemConstant.REDIS_LOCK_WEBSOCKET_TIME_OUT)) {
-//            Object o = redisUtil.get(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT);
-//            int count = 0;
-//            if (Objects.nonNull(o)) {
-//                count = (int) o;
-//            }
-//            count--;
-//            redisUtil.set(SystemConstant.WEBSOCKET_OE_ONLINE_COUNT, count < 0 ? 0 : count);
-//        }
-//    }
+    /**
+     * 获取在线人数
+     *
+     * @return
+     */
+    public synchronized int getOnlineCount() {
+        Object o = redisUtil.get(SystemConstant.WEBSOCKET_MOBILE_ONLINE_COUNT);
+        return Objects.isNull(o) ? 0 : (int) o;
+    }
+
+    /**
+     * 在线人数加一
+     */
+    public synchronized void addOnlineCount() {
+        if (redisUtil.lock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.websocketSessionId, SystemConstant.REDIS_LOCK_WEBSOCKET_TIME_OUT)) {
+            try {
+                Object o = redisUtil.get(SystemConstant.WEBSOCKET_MOBILE_ONLINE_COUNT);
+                int count = 0;
+                if (Objects.nonNull(o)) {
+                    count = (int) o;
+                }
+                count++;
+                redisUtil.set(SystemConstant.WEBSOCKET_MOBILE_ONLINE_COUNT, count);
+            } finally {
+                if (Objects.nonNull(this.websocketSessionId)) {
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.websocketSessionId);
+                }
+            }
+        }
+    }
+
+    /**
+     * 在线人数减一
+     */
+    public synchronized void subOnlineCount() {
+        if (redisUtil.lock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.websocketSessionId, SystemConstant.REDIS_LOCK_WEBSOCKET_TIME_OUT)) {
+            try {
+                Object o = redisUtil.get(SystemConstant.WEBSOCKET_MOBILE_ONLINE_COUNT);
+                int count = 0;
+                if (Objects.nonNull(o)) {
+                    count = (int) o;
+                }
+                count--;
+                redisUtil.set(SystemConstant.WEBSOCKET_MOBILE_ONLINE_COUNT, count < 0 ? 0 : count);
+            } finally {
+                if (Objects.nonNull(this.websocketSessionId)) {
+                    redisUtil.releaseLock(SystemConstant.REDIS_LOCK_WEBSOCKET_PREFIX + this.websocketSessionId);
+                }
+            }
+        }
+    }
 
     public static ConcurrentHashMap<String, WebSocketMobileServer> getWebSocketMap() {
         return webSocketMap;
     }
 
+    /**
+     * 关闭session
+     *
+     * @param webSocketMobileServer
+     */
+    public static void close(WebSocketMobileServer webSocketMobileServer) {
+        //判断当前连接是否还在线
+        if (Objects.nonNull(webSocketMobileServer) && webSocketMobileServer.session.isOpen()) {
+            try {
+                // 关闭连接
+                CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "鉴权失败!");
+                webSocketMobileServer.session.close(closeReason);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
     public Long getRecordId() {
         return recordId;
     }

+ 29 - 26
themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketOeServer.java

@@ -18,6 +18,8 @@ import com.qmth.themis.common.enums.Platform;
 import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.exam.config.ExamConstant;
 import com.qmth.themis.exam.listener.service.MqOeLogicService;
+import com.qmth.themis.exam.websocket.interceptor.WebSocketMobileHandshakeInterceptor;
+import com.qmth.themis.exam.websocket.interceptor.WebSocketOeHandshakeInterceptor;
 import com.qmth.themis.exam.websocketTemplete.WebSocketOeMessageTemplete;
 import com.qmth.themis.mq.templete.Concurrently;
 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
@@ -32,6 +34,7 @@ import javax.websocket.server.ServerEndpoint;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
+import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -45,7 +48,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @Author: wangliang
  * @Date: 2020/7/10
  */
-@ServerEndpoint("/ws/oe")
+@ServerEndpoint(value = "/ws/oe", configurator = WebSocketOeHandshakeInterceptor.class)
 @Component
 public class WebSocketOeServer implements Concurrently {
     private final static Logger log = LoggerFactory.getLogger(WebSocketOeServer.class);
@@ -54,9 +57,8 @@ public class WebSocketOeServer implements Concurrently {
      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
      */
     private Session session = null;
-    private Platform platform = null;
-    private String sessionId = null, ip = null, deviceId = null, authorization = null, url = "/ws/oe", websocketSessionId = null;
-    private Long recordId = null, time = null, updateTime = null;
+    private String sessionId = null, ip = null, deviceId = null, websocketSessionId = null;
+    private Long recordId = null, updateTime = null;
     private RedisUtil redisUtil;
     private Map<String, Object> tranMap = null;
 
@@ -65,27 +67,10 @@ public class WebSocketOeServer implements Concurrently {
      */
     @OnOpen
     public void onOpen(Session session) {
-        Map<String, List<String>> mapParameter = session.getRequestParameterMap();
-        if (Objects.isNull(mapParameter)) {
-            throw new BusinessException(ExceptionResultEnum.PARAMS_ILLEGALITY);
-        }
-        log.info("mapParameter:{}", JacksonUtil.parseJson(mapParameter));
-        log.info("uri:{}", session.getRequestURI());
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
-        }
-        this.platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
-        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
-            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
-        }
-        this.deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
-        this.authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
-        this.time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
-        this.recordId = Long.parseLong(mapParameter.get(Constants.HEADER_RECORD_ID).get(0));
-        redisUtil = SpringContextHolder.getBean(RedisUtil.class);
-
-        TBSession tbSession = AuthUtil.websocketAuthInterceptor(this.platform, this.deviceId, this.authorization, this.time, SystemConstant.GET, this.url);
-        SystemConstant.checkExamStatus(this.recordId);
+        this.redisUtil = SpringContextHolder.getBean(RedisUtil.class);
+        this.recordId = (Long) session.getUserProperties().get(Constants.HEADER_RECORD_ID);
+        this.deviceId = (String) session.getUserProperties().get(Constants.HEADER_DEVICE_ID);
+        TBSession tbSession = (TBSession) session.getUserProperties().get(Constants.HEADER_TB_SESSION);
         this.session = session;
         session.setMaxIdleTimeout(SystemConstant.WEBSOCKET_MAX_TIME_OUT);
         this.sessionId = tbSession.getId();
@@ -118,7 +103,7 @@ public class WebSocketOeServer implements Concurrently {
      * 连接关闭调用的方法
      */
     @OnClose
-    public void onClose() {
+    public void onClose() throws NoSuchAlgorithmException {
         log.info("onClose is come in");
         if (webSocketMap.containsKey(this.websocketSessionId)) {
             webSocketMap.remove(this.websocketSessionId);
@@ -288,6 +273,24 @@ public class WebSocketOeServer implements Concurrently {
         return webSocketMap;
     }
 
+    /**
+     * 关闭session
+     *
+     * @param webSocketOeServer
+     */
+    public static void close(WebSocketOeServer webSocketOeServer) {
+        //判断当前连接是否还在线
+        if (Objects.nonNull(webSocketOeServer) && webSocketOeServer.session.isOpen()) {
+            try {
+                // 关闭连接
+                CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "鉴权失败!");
+                webSocketOeServer.session.close(closeReason);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
     public Long getRecordId() {
         return recordId;
     }

+ 56 - 0
themis-exam/src/main/java/com/qmth/themis/exam/websocket/interceptor/WebSocketMobileHandshakeInterceptor.java

@@ -0,0 +1,56 @@
+package com.qmth.themis.exam.websocket.interceptor;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.entity.TBSession;
+import com.qmth.themis.business.enums.*;
+import com.qmth.themis.business.util.*;
+import com.qmth.themis.common.contanst.Constants;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.enums.Platform;
+import com.qmth.themis.common.exception.BusinessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.websocket.*;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Description: websocket移动端
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/10
+ */
+public class WebSocketMobileHandshakeInterceptor extends ServerEndpointConfig.Configurator {
+    private final static Logger log = LoggerFactory.getLogger(WebSocketMobileHandshakeInterceptor.class);
+
+    public static final String url = "/ws/mobile";
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
+        Map<String, List<String>> mapParameter = request.getParameterMap();
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
+        }
+        Platform platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
+        }
+        String deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
+        String authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
+        Long time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
+        Long recordId = Long.parseLong(mapParameter.get(Constants.HEADER_RECORD_ID).get(0));
+        MonitorVideoSourceEnum source = MonitorVideoSourceEnum.valueOf(mapParameter.get(Constants.HEADER_SOURCE).get(0));
+
+        TBSession tbSession = AuthUtil.websocketAuthInterceptor(platform, deviceId, authorization, time, SystemConstant.GET, url);
+        SystemConstant.checkExamStatus(recordId);
+        config.getUserProperties().put(Constants.HEADER_RECORD_ID, recordId);
+        config.getUserProperties().put(Constants.HEADER_DEVICE_ID, deviceId);
+        config.getUserProperties().put(Constants.HEADER_TB_SESSION, tbSession);
+        config.getUserProperties().put(Constants.HEADER_SOURCE, source);
+    }
+}

+ 54 - 0
themis-exam/src/main/java/com/qmth/themis/exam/websocket/interceptor/WebSocketOeHandshakeInterceptor.java

@@ -0,0 +1,54 @@
+package com.qmth.themis.exam.websocket.interceptor;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.entity.TBSession;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
+import com.qmth.themis.business.util.AuthUtil;
+import com.qmth.themis.common.contanst.Constants;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.enums.Platform;
+import com.qmth.themis.common.exception.BusinessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Description: websocket客户端
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/10
+ */
+public class WebSocketOeHandshakeInterceptor extends ServerEndpointConfig.Configurator {
+    private final static Logger log = LoggerFactory.getLogger(WebSocketOeHandshakeInterceptor.class);
+
+    public static final String url = "/ws/oe";
+
+    @Override
+    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
+        Map<String, List<String>> mapParameter = request.getParameterMap();
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_PLATFORM)) || mapParameter.get(Constants.HEADER_PLATFORM).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
+        }
+        Platform platform = Platform.valueOf(mapParameter.get(Constants.HEADER_PLATFORM).get(0));
+        if (Objects.isNull(mapParameter.get(Constants.HEADER_DEVICE_ID)) || mapParameter.get(Constants.HEADER_DEVICE_ID).size() == 0) {
+            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
+        }
+        String deviceId = mapParameter.get(Constants.HEADER_DEVICE_ID).get(0);
+        String authorization = mapParameter.get(Constants.HEADER_AUTHORIZATION).get(0);
+        Long time = Long.parseLong(mapParameter.get(Constants.HEADER_TIME).get(0));
+        Long recordId = Long.parseLong(mapParameter.get(Constants.HEADER_RECORD_ID).get(0));
+
+        TBSession tbSession = AuthUtil.websocketAuthInterceptor(platform, deviceId, authorization, time, SystemConstant.GET, url);
+        SystemConstant.checkExamStatus(recordId);
+        config.getUserProperties().put(Constants.HEADER_RECORD_ID, recordId);
+        config.getUserProperties().put(Constants.HEADER_DEVICE_ID, deviceId);
+        config.getUserProperties().put(Constants.HEADER_TB_SESSION, tbSession);
+    }
+}

+ 1 - 1
themis-mq/src/main/java/com/qmth/themis/mq/service/MqLogicService.java

@@ -142,7 +142,7 @@ public interface MqLogicService {
      * @param mqDto
      * @param key
      */
-    void execMqExamBreakLogic(MqDto mqDto, String key);
+    void execMqExamBreakLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException;
 
     /**
      * 考试断点延时消息

+ 14 - 69
themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

@@ -22,7 +22,6 @@ import com.qmth.themis.business.templete.TaskImportTemplete;
 import com.qmth.themis.business.templete.impl.*;
 import com.qmth.themis.business.util.*;
 import com.qmth.themis.common.contanst.Constants;
-import com.qmth.themis.common.enums.ExceptionResultEnum;
 import com.qmth.themis.common.enums.Source;
 import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.common.util.SimpleBeanUtil;
@@ -296,61 +295,17 @@ public class MqLogicServiceImpl implements MqLogicService {
         if (Objects.nonNull(websocketStatusEnum) && (Objects.equals(websocketStatusEnum, WebsocketStatusEnum.OFF_LINE)
                 && Objects.equals(status, ExamRecordStatusEnum.ANSWERING))) {
             examRecordService.examBreakLogic(recordId, true);
-            //更新客户端摄像头推流状态为stop
-            MonitorStatusSourceEnum cameraStatusSourceEnum = ExamRecordCacheUtil
-                    .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA);
-            Long timestamp = System.currentTimeMillis();
-            if (Objects.nonNull(cameraStatusSourceEnum)) {
-                ExamRecordCacheUtil
-                        .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA, MonitorStatusSourceEnum.STOP,
-                                timestamp);
-            }
-            //更新客户端屏幕推流状态为stop
-            MonitorStatusSourceEnum screenStatusSourceEnum = ExamRecordCacheUtil
-                    .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN);
-            if (Objects.nonNull(screenStatusSourceEnum)) {
-                ExamRecordCacheUtil
-                        .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN, MonitorStatusSourceEnum.STOP,
-                                timestamp);
-            }
+            SystemConstant.clientMonitorStatusStop(recordId);
 
             ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
             Optional.ofNullable(examStudentCacheBean).orElseThrow(() -> new BusinessException("考生数据为空"));
 
-            TEStudentCacheDto teStudent = cacheService.addStudentAccountCache(examStudentCacheBean.getStudentId());
-            Optional.ofNullable(teStudent).orElseThrow(() -> new BusinessException("学生数据为空"));
-
             //2022/03/28加入更新移动端第一、第二机位推流状态
-            //更新移动端第一机位推流状态为stop
-            MonitorStatusSourceEnum mobileFirstStatusSourceEnum = ExamRecordCacheUtil
-                    .getMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_FIRST);
-            if (Objects.nonNull(mobileFirstStatusSourceEnum)) {
-                ExamRecordCacheUtil
-                        .setMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_FIRST, MonitorStatusSourceEnum.STOP,
-                                timestamp);
-                ExamRecordCacheUtil.setMobileFirstWebsocketStatus(recordId, WebsocketStatusEnum.OFF_LINE, timestamp);
-                String sessionId = SessionUtil.digest(teStudent.getId(),
-                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_FIRST.name());
-                redisUtil.deleteUserSession(sessionId);
-            }
-
-            //更新移动端第二机位推流状态为stop
-            MonitorStatusSourceEnum mobileSecondStatusSourceEnum = ExamRecordCacheUtil
-                    .getMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_SECOND);
-            if (Objects.nonNull(mobileSecondStatusSourceEnum)) {
-                ExamRecordCacheUtil
-                        .setMonitorStatus(recordId, MonitorVideoSourceEnum.MOBILE_SECOND, MonitorStatusSourceEnum.STOP,
-                                timestamp);
-                ExamRecordCacheUtil.setMobileSecondWebsocketStatus(recordId, WebsocketStatusEnum.OFF_LINE, timestamp);
-                String sessionId = SessionUtil.digest(teStudent.getId(),
-                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_SECOND.name());
-                redisUtil.deleteUserSession(sessionId);
-            }
-
-            examRecordService.sendExamRecordDataSaveMq(recordId, timestamp);
+            SystemConstant.mobileMonitorStatusStop(examStudentCacheBean.getStudentId(), recordId, true);
+            examRecordService.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
 
             Map<String, Object> properties = new HashMap<>();
-            properties.put(SystemConstant.MOBILE_REMOVE_WEBSOCKET, true);
+            properties.put(SystemConstant.REMOVE_WEBSOCKET, true);
             //发送移动端监考退出考试mq消息 start
             MqDto mqDtoExamStop = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_STOP.name(), recordId,
                     MqTagEnum.EXAM_STOP, String.valueOf(recordId), properties, String.valueOf(recordId));
@@ -943,7 +898,7 @@ public class MqLogicServiceImpl implements MqLogicService {
      */
     @Override
     @Transactional
-    public void execMqExamBreakLogic(MqDto mqDto, String key) {
+    public void execMqExamBreakLogic(MqDto mqDto, String key) throws NoSuchAlgorithmException {
         ExceptionEnum exceptionEnum = ExceptionEnum.valueOf(String.valueOf(mqDto.getBody()));
         Long recordId = Long.parseLong(mqDto.getObjId());
 
@@ -991,27 +946,17 @@ public class MqLogicServiceImpl implements MqLogicService {
                 ExamRecordCacheUtil.getLastBreakId(recordId), SystemOperationEnum.BREAK_OFF.getTitle());
         teExamStudentLogService.save(teExamStudentLog);
 
-        //更新客户端摄像头推流状态为stop
-        MonitorStatusSourceEnum cameraStatusSourceEnum = ExamRecordCacheUtil
-                .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA);
-        Long timestamp = System.currentTimeMillis();
-        if (Objects.nonNull(cameraStatusSourceEnum)) {
-            ExamRecordCacheUtil
-                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_CAMERA, MonitorStatusSourceEnum.STOP,
-                            timestamp);
-        }
-        //更新客户端屏幕推流状态为stop
-        MonitorStatusSourceEnum screenStatusSourceEnum = ExamRecordCacheUtil
-                .getMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN);
-        if (Objects.nonNull(screenStatusSourceEnum)) {
-            ExamRecordCacheUtil
-                    .setMonitorStatus(recordId, MonitorVideoSourceEnum.CLIENT_SCREEN, MonitorStatusSourceEnum.STOP,
-                            timestamp);
-        }
-        examRecordService.sendExamRecordDataSaveMq(recordId, timestamp);
+        SystemConstant.clientMonitorStatusStop(recordId);
+        examRecordService.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
+
+        //更新移动端第一机位推流状态为stop
+        SystemConstant.mobileMonitorStatusStop(examStudentCacheBean.getStudentId(), recordId, true);
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(SystemConstant.REMOVE_WEBSOCKET, true);
         //发送移动端监考退出考试mq消息 start
         MqDto mqDtoExamStop = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXAM_STOP.name(), recordId,
-                MqTagEnum.EXAM_STOP, String.valueOf(recordId), String.valueOf(recordId));
+                MqTagEnum.EXAM_STOP, String.valueOf(recordId), properties, String.valueOf(recordId));
         mqDtoService.assembleSendOneOrderMsg(mqDtoExamStop);
         //发送移动端监考退出考试mq消息 end