Sfoglia il codice sorgente

修改考生登录部分逻辑,增加学生锁;候考接口增加未完成考试状态拦截;增加清除未完成考试记录判断

luoshi 3 anni fa
parent
commit
9212255178

+ 54 - 39
themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java

@@ -54,9 +54,9 @@ public class SystemConstant {
     /**
      * 腾讯云
      */
-//    public static final String TENCENT_APPID = "appId";
+    //    public static final String TENCENT_APPID = "appId";
 
-//    public static final String TENCENT_KEY = "key";
+    //    public static final String TENCENT_KEY = "key";
 
     public static final long TENCENT_EXPIRE_TIME = 24 * 3600 * 30;//30天过期
 
@@ -217,9 +217,9 @@ public class SystemConstant {
 
     public static String TEMP_FILES_DIR;
 
-//    public static final String MONITOR_LIVE_URL_ = "monitorLiveUrl_";
+    //    public static final String MONITOR_LIVE_URL_ = "monitorLiveUrl_";
 
-//    public static final String STREAM_NAME = "streamName_";
+    //    public static final String STREAM_NAME = "streamName_";
 
     public static final String MONITOR_STATUS_ = "monitorStatus_";
 
@@ -241,7 +241,7 @@ public class SystemConstant {
     public static final int MOBILE_SESSION_EXPIRE = 30;//过期时间30天
 
     //二维码过期时间(秒)
-//    public final static Long QR_EXPIRE_TIME = 30L;
+    //    public final static Long QR_EXPIRE_TIME = 30L;
 
     /**
      * redis分布式锁
@@ -269,6 +269,8 @@ public class SystemConstant {
     //学生锁
     public static final String REDIS_LOCK_STUDENT_PREFIX = "lock:student:student_id_";
 
+    public static final long REDIS_LOCK_STUDENT_TIME_OUT = 30L;
+
     //考生锁
     public static final String REDIS_LOCK_EXAM_STUDENT_PREFIX = "lock:student:student_id_";
 
@@ -311,7 +313,7 @@ public class SystemConstant {
     /**
      * rocket mq
      */
-//    public static final String MQDTO_OBJ = "mqDtoObj";
+    //    public static final String MQDTO_OBJ = "mqDtoObj";
 
     public static final int CONSUME_MESSAGE_BATCH_MAX_SIZE = 10;
 
@@ -343,15 +345,15 @@ public class SystemConstant {
     /**
      * 线程池配置
      */
-//    public static final String THREAD_POOL_NAME = "arbitrateThreadPool";
-//
-//    public static final int THREAD_POOL_CORE_POOL_SIZE = 20;
-//
-//    public static final int THREAD_POOL_MAX_POOL_SIZE = 40;
-//
-//    public static final int THREAD_POOL_KEEP_ALIVE_SECONDS = 60;
-//
-//    public static final int THREAD_POOL_QUEUE_CAPACITY = 100;
+    //    public static final String THREAD_POOL_NAME = "arbitrateThreadPool";
+    //
+    //    public static final int THREAD_POOL_CORE_POOL_SIZE = 20;
+    //
+    //    public static final int THREAD_POOL_MAX_POOL_SIZE = 40;
+    //
+    //    public static final int THREAD_POOL_KEEP_ALIVE_SECONDS = 60;
+    //
+    //    public static final int THREAD_POOL_QUEUE_CAPACITY = 100;
 
     /**
      * websocket
@@ -372,12 +374,19 @@ public class SystemConstant {
      * 缓存配置
      */
     public static final String userOauth = "user:oauth:cache";
+
     public static final String studentOauth = "student:oauth:cache";
+
     public static final String userAccount = "user:account:cache";
+
     public static final String studentAccount = "student:account:cache";
+
     public static final String orgCache = "org:cache";
+
     public static final String orgCodeCache = "org:code:cache";
+
     public static final String roleCache = "role:cache";
+
     public static final String configCache = "config:cache";
 
     //    /**
@@ -408,24 +417,24 @@ public class SystemConstant {
         calendar.setTime(now);
         long redisExpire = REDIS_EXPIRE_TIME;
         switch (source) {
-            case ADMIN_WEB:
-                calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.ADMIN_WEB_SESSION_EXPIRE);
-                break;
-            case ADMIN_CLIENT:
-                calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.ADMIN_CLIENT_SESSION_EXPIRE);
-                break;
-            case OE_CLIENT:
-                calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.OE_CLIENT_SESSION_EXPIRE);
-                redisExpire = REDIS_CLIENT_EXPIRE_TIME;
-                break;
-            case OE_ANSWER:
-            case MOBILE_MONITOR_FIRST:
-            case MOBILE_MONITOR_SECOND:
-                calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.MOBILE_SESSION_EXPIRE);
-                redisExpire = REDIS_PHONE_EXPIRE_TIME;
-                break;
-            default:
-                break;
+        case ADMIN_WEB:
+            calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.ADMIN_WEB_SESSION_EXPIRE);
+            break;
+        case ADMIN_CLIENT:
+            calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.ADMIN_CLIENT_SESSION_EXPIRE);
+            break;
+        case OE_CLIENT:
+            calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.OE_CLIENT_SESSION_EXPIRE);
+            redisExpire = REDIS_CLIENT_EXPIRE_TIME;
+            break;
+        case OE_ANSWER:
+        case MOBILE_MONITOR_FIRST:
+        case MOBILE_MONITOR_SECOND:
+            calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.MOBILE_SESSION_EXPIRE);
+            redisExpire = REDIS_PHONE_EXPIRE_TIME;
+            break;
+        default:
+            break;
         }
         dto.setDate(calendar.getTime());
         dto.setExpireSeconds(redisExpire);
@@ -526,8 +535,9 @@ public class SystemConstant {
         if (Objects.isNull(examRecordStatusEnum)) {
             throw new BusinessException(ExceptionResultEnum.EXAM_FINISH);
         }
-        if (!Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects.equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum)
-                && !Objects.equals(ExamRecordStatusEnum.ANSWERING, examRecordStatusEnum)) {
+        if (!Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects
+                .equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum) && !Objects
+                .equals(ExamRecordStatusEnum.ANSWERING, examRecordStatusEnum)) {
             throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
         }
     }
@@ -547,7 +557,9 @@ public class SystemConstant {
         } else if (videoSource.contains(MonitorVideoSourceEnum.MOBILE_SECOND.name().toLowerCase())) {
             monitorVideoSourceEnum = MonitorVideoSourceEnum.MOBILE_SECOND;
         }
-        return videoSource.substring(0, videoSource.indexOf(monitorVideoSourceEnum.name().toLowerCase()) + monitorVideoSourceEnum.name().length());
+        return videoSource.substring(0,
+                videoSource.indexOf(monitorVideoSourceEnum.name().toLowerCase()) + monitorVideoSourceEnum.name()
+                        .length());
     }
 
     /**
@@ -583,7 +595,8 @@ public class SystemConstant {
      * @param deleteSession
      * @throws NoSuchAlgorithmException
      */
-    public static void mobileMonitorStatusStop(Long studentId, Long recordId, boolean 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();
@@ -602,7 +615,8 @@ public class SystemConstant {
             //是否删除session
             if (deleteSession) {
                 String sessionId = SessionUtil.digest(teStudent.getId(),
-                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_FIRST.name());
+                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()),
+                        Source.MOBILE_MONITOR_FIRST.name());
                 redisUtil.deleteUserSession(sessionId);
             }
         }
@@ -618,7 +632,8 @@ public class SystemConstant {
             //是否删除session
             if (deleteSession) {
                 String sessionId = SessionUtil.digest(teStudent.getId(),
-                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()), Source.MOBILE_MONITOR_SECOND.name());
+                        Math.abs(Sets.newHashSet(RoleEnum.STUDENT.name()).toString().hashCode()),
+                        Source.MOBILE_MONITOR_SECOND.name());
                 redisUtil.deleteUserSession(sessionId);
             }
         }

+ 62 - 45
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamServiceImpl.java

@@ -135,7 +135,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
     @SuppressWarnings("rawtypes")
     @Override
     public IPage<TEExamQueryDto> examQuery(IPage<Map> iPage, Long userId, Long id, String code, String name,
-                                           String mode, Integer enable, Long orgId, String type) {
+            String mode, Integer enable, Long orgId, String type) {
         return teExamMapper.examQuery(iPage, userId, id, code, name, mode, enable, orgId, type);
     }
 
@@ -159,7 +159,8 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                     examActivityIdsTemp.add(t.getExamActivityId());
                 }
             }
-            Set<Long> examActivityIds = examActivityIdsTemp.stream().map(s -> Long.parseLong(s)).collect(Collectors.toSet());
+            Set<Long> examActivityIds = examActivityIdsTemp.stream().map(s -> Long.parseLong(s))
+                    .collect(Collectors.toSet());
             List<TEExamActivityWaitDto> teExamActivityWaitList = teExamActivityService
                     .getWaitingExam(studentId, examActivityIds, null);
             Map<Long, Set<TEExamActivityWaitDto>> map = new HashMap<>();
@@ -234,13 +235,15 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         if (examCache.getEnable().intValue() == 0) {
             throw new BusinessException(ExceptionResultEnum.EXAM_ENABLE);
         }
+        if (ExamingDataCacheUtil.getUnFinishedRecordId(studentId) != null) {
+            throw new BusinessException(ExceptionResultEnum.EXAM_RECORD_UNFINISHED);
+        }
         checkIp(examCache);
         Long activityId = es.getExamActivityId();
         Long examId = es.getExamId();
         QueryWrapper<TOeExamRecord> tOeExamRecordQueryWrapper = new QueryWrapper<>();
         tOeExamRecordQueryWrapper.lambda().eq(TOeExamRecord::getExamId, examId)
-                .eq(TOeExamRecord::getExamActivityId, activityId)
-                .eq(TOeExamRecord::getExamStudentId, examStudentId)
+                .eq(TOeExamRecord::getExamActivityId, activityId).eq(TOeExamRecord::getExamStudentId, examStudentId)
                 .eq(TOeExamRecord::getStatus, ExamRecordStatusEnum.FIRST_PREPARE);
         TOeExamRecord tOeExamRecord = toeExamRecordService.getOne(tOeExamRecordQueryWrapper);
         Long unFinishedRecordId = Objects.nonNull(tOeExamRecord) ? tOeExamRecord.getId() : null;
@@ -252,12 +255,13 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 ExamCourseCacheBean ec = teExamCourseService.getExamCourseCacheBean(es.getExamId(), es.getCourseCode());
                 ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
                 ExamActivityCacheBean ac = teExamActivityService.getExamActivityCacheBean(es.getExamActivityId());
-                ExamPrepareBean prepare = new ExamPrepareBean(recordId, (ec.getObjectiveShuffle() == null || ec.getObjectiveShuffle().intValue() == 0 ? false : true),
+                ExamPrepareBean prepare = new ExamPrepareBean(recordId,
+                        (ec.getObjectiveShuffle() == null || ec.getObjectiveShuffle().intValue() == 0 ? false : true),
                         (ec.getOptionShuffle() == null || ec.getOptionShuffle().intValue() == 0 ? false : true),
-                        (ep.getHasAudio() == null || ep.getHasAudio().intValue() == 0 ? false : true)
-                        , ep.getAudioPlayCount(), ExamRecordCacheUtil.getMonitorKey(recordId), monitorUserId
-                        , tencentYunUtil.getSign(monitorUserId, SystemConstant.TENCENT_EXPIRE_TIME)
-                        , tencentYunUtil.getTencentYunDomain().getAppId());
+                        (ep.getHasAudio() == null || ep.getHasAudio().intValue() == 0 ? false : true),
+                        ep.getAudioPlayCount(), ExamRecordCacheUtil.getMonitorKey(recordId), monitorUserId,
+                        tencentYunUtil.getSign(monitorUserId, SystemConstant.TENCENT_EXPIRE_TIME),
+                        tencentYunUtil.getTencentYunDomain().getAppId());
                 ExamRecordStatusEnum statusEnum = ExamRecordCacheUtil.getStatus(recordId);
                 boolean cache = false;
                 if (Objects.nonNull(statusEnum) && !Objects.equals(statusEnum, ExamRecordStatusEnum.RESUME_PREPARE)) {
@@ -323,22 +327,24 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 .saveByPrepare(es.getExamId(), es.getExamActivityId(), examStudentId, paperId,
                         es.getAlreadyExamCount() + 1, studentId);
 
-
         es.setCurrentRecordId(recordId);
-        ExamPrepareBean prepare = new ExamPrepareBean(recordId, (ec.getObjectiveShuffle() == null || ec.getObjectiveShuffle().intValue() == 0 ? false : true),
+        ExamPrepareBean prepare = new ExamPrepareBean(recordId,
+                (ec.getObjectiveShuffle() == null || ec.getObjectiveShuffle().intValue() == 0 ? false : true),
                 (ec.getOptionShuffle() == null || ec.getOptionShuffle().intValue() == 0 ? false : true),
-                (ep.getHasAudio() == null || ep.getHasAudio().intValue() == 0 ? false : true)
-                , ep.getAudioPlayCount(), ExamRecordCacheUtil.getMonitorKey(recordId), monitorUserId
-                , tencentYunUtil.getSign(monitorUserId, SystemConstant.TENCENT_EXPIRE_TIME)
-                , tencentYunUtil.getTencentYunDomain().getAppId());
-        TEExamActivityDto teExamActivityDto = teExamActivityService.getWaitingExam(activityId, examStudentId, es.getCourseCode(), monitorUtil.getMonitorDomain().getPrefix(), recordId);
+                (ep.getHasAudio() == null || ep.getHasAudio().intValue() == 0 ? false : true), ep.getAudioPlayCount(),
+                ExamRecordCacheUtil.getMonitorKey(recordId), monitorUserId,
+                tencentYunUtil.getSign(monitorUserId, SystemConstant.TENCENT_EXPIRE_TIME),
+                tencentYunUtil.getTencentYunDomain().getAppId());
+        TEExamActivityDto teExamActivityDto = teExamActivityService
+                .getWaitingExam(activityId, examStudentId, es.getCourseCode(),
+                        monitorUtil.getMonitorDomain().getPrefix(), recordId);
         prepare.setTeExamActivityDto(teExamActivityDto);
 
         // 更新考生缓存
         redisUtil.set(RedisKeyHelper.examStudentCacheKey(examStudentId), es);
         //更新场次-考试记录缓存
-//        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId);
-//        ExamingDataCacheUtil.setUnFinishedRecordId(studentId, recordId);
+        //        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId);
+        //        ExamingDataCacheUtil.setUnFinishedRecordId(studentId, recordId);
         //mq发送消息start
         //        TEStudentCacheDto teStudentCacheDto = (TEStudentCacheDto) redisUtil.getStudent(studentId);
         //        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.STUDENT.name(), SystemOperationEnum.FIRST_PREPARE, MqTagEnum.STUDENT, String.valueOf(teStudentCacheDto.getId()), teStudentCacheDto.getIdentity());
@@ -463,14 +469,14 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                 if (Objects.isNull(clientCameraStatus) || Objects
                         .equals(MonitorStatusSourceEnum.STOP, clientCameraStatus)) {
                     switch (strs[i]) {
-                        case "CLIENT_CAMERA":
-                            throw new BusinessException(ExceptionResultEnum.CLIENT_CAMERA_OFFLINE);
-                        case "CLIENT_SCREEN":
-                            throw new BusinessException(ExceptionResultEnum.CLIENT_SCREEN_OFFLINE);
-                        case "MOBILE_FIRST":
-                            throw new BusinessException(ExceptionResultEnum.MOBILE_FIRST_OFFLINE);
-                        default:
-                            throw new BusinessException(ExceptionResultEnum.MOBILE_SECOND_OFFLINE);
+                    case "CLIENT_CAMERA":
+                        throw new BusinessException(ExceptionResultEnum.CLIENT_CAMERA_OFFLINE);
+                    case "CLIENT_SCREEN":
+                        throw new BusinessException(ExceptionResultEnum.CLIENT_SCREEN_OFFLINE);
+                    case "MOBILE_FIRST":
+                        throw new BusinessException(ExceptionResultEnum.MOBILE_FIRST_OFFLINE);
+                    default:
+                        throw new BusinessException(ExceptionResultEnum.MOBILE_SECOND_OFFLINE);
                     }
                 }
             }
@@ -505,7 +511,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                         }
                         toeExamRecordService.sendExamRecordDataSaveMq(recordId, timestamp);
                         //更新场次-考试记录缓存
-//                        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId);
+                        //                        ExamActivityRecordCacheUtil.setExamRecordStatus(activityId, recordId);
                     } else {
                         throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ALREADY_UPDATE);
                     }
@@ -607,7 +613,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public AnswerSubmitBean answerSubmit(Long studentId, Long recordId, Integer mainNumber, Integer subNumber,
-                                         Integer subIndex, String answer, Long version, Integer durationSeconds) {
+            Integer subIndex, String answer, Long version, Integer durationSeconds) {
 
         // 校验当前登录用户和参数一致性
         if (ExamRecordCacheUtil.getId(recordId) == null) {
@@ -789,7 +795,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public AudioLeftPlayCountSubmitBean audioLeftPlayCountSubmit(Long studentId, Long recordId, String key,
-                                                                 Integer count) {
+            Integer count) {
 
         // 校验当前登录用户和参数一致性
         if (ExamRecordCacheUtil.getId(recordId) == null) {
@@ -838,7 +844,9 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             throw new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_STUDENT);
         }
 
-        String filePath = "upload" + File.separator + sdf.format(new Date()) + File.separator + SystemConstant.getUuid() + "." + suffix;
+        String filePath =
+                "upload" + File.separator + sdf.format(new Date()) + File.separator + SystemConstant.getUuid() + "."
+                        + suffix;
         InputStream in = null;
         try {
             in = file.getInputStream();
@@ -906,8 +914,8 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         }
         ret = new ExamResumeBean();
         ret.setDurationSeconds(ExamRecordCacheUtil.getDurationSeconds(recordId));
-//        ret.setPaperUrl(ossUtil.getPrivateUrl(ep.getPaperPath()));
-//        ret.setStructUrl(ossUtil.getPrivateUrl(ep.getStructPath()));
+        //        ret.setPaperUrl(ossUtil.getPrivateUrl(ep.getPaperPath()));
+        //        ret.setStructUrl(ossUtil.getPrivateUrl(ep.getStructPath()));
         ret.setHasAudio((ep.getHasAudio() != null && ep.getHasAudio().intValue() == 1 ? true : false));
         ret.setAudioPlayCount(ep.getAudioPlayCount());
         ret.setMonitorAppId(tencentYunUtil.getTencentYunDomain().getAppId());
@@ -916,9 +924,10 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         ret.setMonitorUserSig(tencentYunUtil.getSign(ret.getMonitorUserId(), SystemConstant.TENCENT_EXPIRE_TIME));
 
         ExamActivityCacheBean ac = teExamActivityService.getExamActivityCacheBean(es.getExamActivityId());
-        ExamCourseCacheBean examCourseCacheBean = teExamCourseService.getExamCourseCacheBean(es.getExamId(), es.getCourseCode());
-        TEExamActivityDto teExamActivityDto = new TEExamActivityDto(ec, ac, es,
-                examStudentId, examCourseCacheBean, recordId, monitorUtil.getMonitorDomain().getPrefix(), true);
+        ExamCourseCacheBean examCourseCacheBean = teExamCourseService
+                .getExamCourseCacheBean(es.getExamId(), es.getCourseCode());
+        TEExamActivityDto teExamActivityDto = new TEExamActivityDto(ec, ac, es, examStudentId, examCourseCacheBean,
+                recordId, monitorUtil.getMonitorDomain().getPrefix(), true);
         ret.setTeExamActivityDto(teExamActivityDto);
 
         ExamStudentPaperStructCacheBean struct = (ExamStudentPaperStructCacheBean) redisUtil
@@ -956,7 +965,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                         }
                         toeExamRecordService.sendExamRecordDataSaveMq(recordId, lastPrepareTime);
                         //更新场次-考试记录缓存
-//                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId);
+                        //                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId);
                     } else {
                         throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ALREADY_UPDATE);
                     }
@@ -1086,7 +1095,6 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
             return ret;
         }
 
-
         ExamCacheBean exam = getExamCacheBeanNative(es.getExamId());
         ret.setPostNotice(exam.getPostNotice());
         ExamRecordStatusEnum sta = ExamRecordCacheUtil.getStatus(recordId);
@@ -1156,7 +1164,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
                             && examRecordStatusEnum == ExamRecordStatusEnum.FINISHED) {
                         toeExamRecordService.sendExamRecordDataSaveMq(recordId, finishTime);
                         //更新场次-考试记录缓存
-//                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId);
+                        //                        ExamActivityRecordCacheUtil.setExamRecordStatus(es.getExamActivityId(), recordId);
                     } else {
                         throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ALREADY_UPDATE);
                     }
@@ -1180,15 +1188,21 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
         }
         //更新未完成考试记录id
         TEStudentCacheDto teStudentCacheDto = cacheService.addStudentAccountCache(es.getStudentId());
-        ExamingDataCacheUtil.deleteUnFinishedRecordId(studentId);
+        //判断当前结束的考试记录ID是否是未完成的考试记录ID
+        if (recordId.equals(ExamingDataCacheUtil.getUnFinishedRecordId(studentId))) {
+            ExamingDataCacheUtil.deleteUnFinishedRecordId(studentId);
+        }
 
         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));
+        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));
+        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);
 
         //异步持久化
@@ -1202,9 +1216,12 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
 
         if (Objects.nonNull(monitorRecord) && !Objects.equals(monitorRecord.trim().replaceAll(" ", ""), "")) {
             //发送腾讯云回调延时mq消息start
-            Map<String, Object> tranMap = mqDtoService.buildMqDelayMsg(tencentYunUtil.getTencentYunDomain().getCallbackTime());
+            Map<String, Object> tranMap = mqDtoService
+                    .buildMqDelayMsg(tencentYunUtil.getTencentYunDomain().getCallbackTime());
             tranMap.put(SystemConstant.RECORD_ID, recordId);
-            MqDto mqDtoTencentVideo = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.TENCENT_VIDEO.name(), MqTagEnum.TENCENT_VIDEO.name(), MqTagEnum.TENCENT_VIDEO, String.valueOf(recordId), tranMap, String.valueOf(recordId));
+            MqDto mqDtoTencentVideo = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.TENCENT_VIDEO.name(),
+                    MqTagEnum.TENCENT_VIDEO.name(), MqTagEnum.TENCENT_VIDEO, String.valueOf(recordId), tranMap,
+                    String.valueOf(recordId));
             mqDtoService.assembleSendAsyncDelayMsg(mqDtoTencentVideo);
             //发送腾讯云回调延时mq消息end
         }
@@ -1216,7 +1233,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      *
      * @param recordId
      */
-    private void checkToPersisted(Long recordId,Long studentId) {
+    private void checkToPersisted(Long recordId, Long studentId) {
         ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
         if (!ExamRecordStatusEnum.FINISHED.equals(status)) {
             return;
@@ -1380,7 +1397,7 @@ public class TEExamServiceImpl extends ServiceImpl<TEExamMapper, TEExam> impleme
      */
     @Override
     public void sendOeLogMessage(SystemOperationEnum systemOperationEnum, Long examStudentId, Long recordId,
-                                 MqDto mqDto) {
+            MqDto mqDto) {
         //mq发送消息start
         Map<String, Object> properties = new HashMap<>();
         properties.put(SystemConstant.REMARK, systemOperationEnum.getCode());

+ 6 - 2
themis-common/src/main/java/com/qmth/themis/common/enums/ExceptionResultEnum.java

@@ -15,7 +15,7 @@ public enum ExceptionResultEnum {
 
     NOT_FOUND_EXAM_STUDENT(500, 500021, "未找到考生"),
 
-//    EXAM_ALREADY_FINISHED(500,500022,"该考试已结束"),
+    //    EXAM_ALREADY_FINISHED(500,500022,"该考试已结束"),
 
     EXAM_ID_NOT_EQUALY(500, 500023, "考试记录的学生Id和当前登录用户不一致"),
 
@@ -25,7 +25,7 @@ public enum ExceptionResultEnum {
 
     STUDENT_NOT_ALLOW_LOGIN(500, 500026, "该学生正在考试,不能登录"),
 
-//    EXAM_STATUS_NOT_NULL(500,500027,"考试状态不能为空"),
+    //    EXAM_STATUS_NOT_NULL(500,500027,"考试状态不能为空"),
 
     EXAM_STATUS_NOT_ALLOW_IP(500, 500028, "考生IP不被允许"),
 
@@ -59,6 +59,8 @@ public enum ExceptionResultEnum {
 
     FORCE_FINISH(500, 500043, "已超过集中统一收卷时间"),
 
+    EXAM_RECORD_UNFINISHED(500, 500044, "当前考试未结束,不允许开始新考试的候考"),
+
     /**
      * 系统预置
      */
@@ -268,7 +270,9 @@ public enum ExceptionResultEnum {
     REPEAT_CONNECT_ERROR(500, 5000025, "不允许重复连接");
 
     private int statusCode;
+
     private int code;
+
     private String message;
 
     ExceptionResultEnum(int code, int statusCode, String message) {

+ 73 - 59
themis-exam/src/main/java/com/qmth/themis/exam/api/TEExamController.java

@@ -67,17 +67,15 @@ public class TEExamController {
 
     @ApiOperation(value = "验证考试口令接口")
     @RequestMapping(value = "/short_code", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "考试信息", response = TEExam.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "考试信息", response = TEExam.class) })
     public Result shortCode(@ApiJsonObject(name = "shortCode", value = {
-            @ApiJsonProperty(key = "shortCode", description = "考试口令")
-    }) @ApiParam(value = "考试口令", required = true) @RequestBody Map<String, Object> mapParameter) {
+            @ApiJsonProperty(key = "shortCode", description = "考试口令") }) @ApiParam(value = "考试口令", required = true) @RequestBody Map<String, Object> mapParameter) {
         if (Objects.isNull(mapParameter.get("shortCode")) || Objects.equals(mapParameter.get("shortCode"), "")) {
             throw new BusinessException(ExceptionResultEnum.SHORT_CODE_IS_NULL);
         }
         String shortCode = String.valueOf(mapParameter.get("shortCode"));
         QueryWrapper<TEExam> teExamQueryWrapper = new QueryWrapper<>();
-        teExamQueryWrapper.lambda().eq(TEExam::getShortCode, shortCode)
-                .eq(TEExam::getEnable, 1)
+        teExamQueryWrapper.lambda().eq(TEExam::getShortCode, shortCode).eq(TEExam::getEnable, 1)
                 .eq(TEExam::getArchived, 1);
         TEExam teExam = teExamService.getOne(teExamQueryWrapper);
         if (Objects.isNull(teExam)) {
@@ -91,11 +89,12 @@ public class TEExamController {
 
     @ApiOperation(value = "获取待考列表接口")
     @RequestMapping(value = "/waiting", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "考试列表", response = TEExamDto.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "考试列表", response = TEExamDto.class) })
     public Result waiting(@ApiJsonObject(name = "examWaiting", value = {
-            @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id")
-    }) @ApiParam(value = "考试口令", required = true) @RequestBody Map<String, Object> mapParameter) {
-        Long examId = Objects.nonNull(mapParameter.get(SystemConstant.EXAM_ID)) ? Long.parseLong(String.valueOf(mapParameter.get(SystemConstant.EXAM_ID))) : null;
+            @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id") }) @ApiParam(value = "考试口令", required = true) @RequestBody Map<String, Object> mapParameter) {
+        Long examId = Objects.nonNull(mapParameter.get(SystemConstant.EXAM_ID)) ?
+                Long.parseLong(String.valueOf(mapParameter.get(SystemConstant.EXAM_ID))) :
+                null;
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         List<TEExamWaitDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, teStudent.getOrgId());
         return ResultUtil.ok(Collections.singletonMap("waiting", list));
@@ -103,12 +102,11 @@ public class TEExamController {
 
     @ApiOperation(value = "开始候考")
     @RequestMapping(value = "/prepare", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "候考信息", response = ExamPrepareBean.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "候考信息", response = ExamPrepareBean.class) })
     public Result prepare(@RequestBody PrepareParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + teStudent.getId();
-        Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
-        if (!lock) {
+        if (!redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT)) {
             throw new BusinessException(ExceptionResultEnum.REQUEST_AWAIT);
         }
         try {
@@ -123,11 +121,12 @@ public class TEExamController {
 
     @ApiOperation(value = "候考/答题状态退出")
     @RequestMapping(value = "/exit", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class) })
     public Result 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) throws NoSuchAlgorithmException, IOException {
-        if (Objects.isNull(mapParameter.get(SystemConstant.RECORD_ID)) || Objects.equals(mapParameter.get(SystemConstant.RECORD_ID), "")) {
+            @ApiJsonProperty(key = "recordId", type = "long", example = "1", description = "考试记录id", required = true) }) @ApiParam(value = "考试记录id", required = true) @RequestBody Map<String, Object> mapParameter)
+            throws NoSuchAlgorithmException, IOException {
+        if (Objects.isNull(mapParameter.get(SystemConstant.RECORD_ID)) || Objects
+                .equals(mapParameter.get(SystemConstant.RECORD_ID), "")) {
             throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
         }
         String recordId = (String) mapParameter.get(SystemConstant.RECORD_ID);
@@ -153,12 +152,11 @@ public class TEExamController {
 
     @ApiOperation(value = "开始考试")
     @RequestMapping(value = "/start", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result start(@RequestBody ExamStartParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + teStudent.getId();
-        Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
-        if (!lock) {
+        if (!redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT)) {
             throw new BusinessException(ExceptionResultEnum.REQUEST_AWAIT);
         }
         try {
@@ -170,20 +168,26 @@ public class TEExamController {
                 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.get(SystemConstant.TYPE))));
+                    ExceptionEnum exceptionEnum = ExceptionEnum
+                            .valueOf(ExceptionEnum.convertToName(String.valueOf(jsonObject.get(SystemConstant.TYPE))));
                     String reason = String.valueOf(jsonObject.get("reason"));
                     ExamBreakCacheUtil.setBreakReason(breakId, exceptionEnum);
                     ExamBreakCacheUtil.setResumeReason(breakId, reason);
                     tOeExamBreakHistoryService.sendExamRecordBreakHistoryDataMq(breakId);
                     //考试断点异常原因 发送mq start
-                    MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXCEPTION_LOG.name(), JacksonUtil.parseJson(param), MqTagEnum.EXCEPTION_LOG, String.valueOf(param.getRecordId()), param.getReason());
+                    MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.EXCEPTION_LOG.name(),
+                            JacksonUtil.parseJson(param), MqTagEnum.EXCEPTION_LOG, String.valueOf(param.getRecordId()),
+                            param.getReason());
                     mqDtoService.assembleSendOneWayMsg(mqDto);
                     //考试断点异常原因 发送mq end
                 }
             } else {
                 //mq发送消息start
-                MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.STUDENT.name(), SystemOperationEnum.ANSWERING, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()), teStudent.getIdentity());
-                teExamService.sendOeLogMessage(SystemOperationEnum.ANSWERING, ExamRecordCacheUtil.getExamStudentId(param.getRecordId()), param.getRecordId(), mqDto);
+                MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.STUDENT.name(),
+                        SystemOperationEnum.ANSWERING, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()),
+                        teStudent.getIdentity());
+                teExamService.sendOeLogMessage(SystemOperationEnum.ANSWERING,
+                        ExamRecordCacheUtil.getExamStudentId(param.getRecordId()), param.getRecordId(), mqDto);
                 //mq发送消息end
             }
             ExamConstant.sendExamStartMsg(param.getRecordId());
@@ -195,7 +199,7 @@ public class TEExamController {
 
     @ApiOperation(value = "上传个人试卷结构")
     @RequestMapping(value = "/student_paper_struct/upload", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result studentPaperStruct(@RequestBody StudentPaperStructParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
@@ -210,7 +214,7 @@ public class TEExamController {
 
     @ApiOperation(value = "提交作答结果")
     @RequestMapping(value = "/answer/submit", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result answerSubmit(@RequestBody AnswerSubmitParamBean param) throws IOException {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
@@ -229,31 +233,31 @@ public class TEExamController {
             throw new BusinessException("时间戳不能为空");
         }
 
-//        Map mqMap = new HashMap<>();
-//        mqMap.put(SystemConstant.RECORD_ID, param.getRecordId());
-//        mqMap.put("source", MonitorVideoSourceEnum.MOBILE_FIRST.name());
-//        //监控结束
-//        MqDto mqDtoStop = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.MONITOR_STOP.name(), param.getRecordId(), MqTagEnum.MONITOR_STOP, String.valueOf(param.getRecordId()), mqMap, String.valueOf(param.getRecordId()));
-//        mqDtoService.assembleSendOneOrderMsg(mqDtoStop);
+        //        Map mqMap = new HashMap<>();
+        //        mqMap.put(SystemConstant.RECORD_ID, param.getRecordId());
+        //        mqMap.put("source", MonitorVideoSourceEnum.MOBILE_FIRST.name());
+        //        //监控结束
+        //        MqDto mqDtoStop = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.MONITOR_STOP.name(), param.getRecordId(), MqTagEnum.MONITOR_STOP, String.valueOf(param.getRecordId()), mqMap, String.valueOf(param.getRecordId()));
+        //        mqDtoService.assembleSendOneOrderMsg(mqDtoStop);
 
-//        ExamRecordCacheUtil.setStatus(param.getRecordId(), ExamRecordStatusEnum.FINISHED, System.currentTimeMillis());
-//        ConcurrentHashMap<String, WebSocketOeServer> webSocketMap = WebSocketOeServer.getWebSocketMap();
-//        String clientWebsocketId = ExamRecordCacheUtil.getClientWebsocketId(param.getRecordId());
-//        if (Objects.nonNull(clientWebsocketId)) {
-//            WebSocketOeServer webSocketOeServer = webSocketMap.get(clientWebsocketId);
-//            WebSocketOeServer.close(webSocketOeServer);
-//        }
+        //        ExamRecordCacheUtil.setStatus(param.getRecordId(), ExamRecordStatusEnum.FINISHED, System.currentTimeMillis());
+        //        ConcurrentHashMap<String, WebSocketOeServer> webSocketMap = WebSocketOeServer.getWebSocketMap();
+        //        String clientWebsocketId = ExamRecordCacheUtil.getClientWebsocketId(param.getRecordId());
+        //        if (Objects.nonNull(clientWebsocketId)) {
+        //            WebSocketOeServer webSocketOeServer = webSocketMap.get(clientWebsocketId);
+        //            WebSocketOeServer.close(webSocketOeServer);
+        //        }
 
-        AnswerSubmitBean ret = teExamService.answerSubmit(teStudent.getId(), param.getRecordId(), param.getMainNumber(),
-                param.getSubNumber(), param.getSubIndex(), param.getAnswer(), param.getVersion(),
-                param.getDurationSeconds());
+        AnswerSubmitBean ret = teExamService
+                .answerSubmit(teStudent.getId(), param.getRecordId(), param.getMainNumber(), param.getSubNumber(),
+                        param.getSubIndex(), param.getAnswer(), param.getVersion(), param.getDurationSeconds());
         return ResultUtil.ok(ret);
-//        return ResultUtil.ok(true);
+        //        return ResultUtil.ok(true);
     }
 
     @ApiOperation(value = "更新音频剩余播放次数")
     @RequestMapping(value = "/audio_left_play_count/submit", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result audioLeftPlayCountSubmit(@RequestBody AudioLeftPlayCountSubmitParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
@@ -265,23 +269,24 @@ public class TEExamController {
         if (param.getCount() == null) {
             throw new BusinessException("剩余播放次数不能为空");
         }
-        return ResultUtil.ok(teExamService.audioLeftPlayCountSubmit(teStudent.getId(), param.getRecordId(), param.getKey(), param.getCount()));
+        return ResultUtil.ok(teExamService
+                .audioLeftPlayCountSubmit(teStudent.getId(), param.getRecordId(), param.getKey(), param.getCount()));
     }
 
     @ApiOperation(value = "文件上传")
     @RequestMapping(value = "/file/upload", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result fileUpload(@ApiParam(value = "考试记录ID", required = true) @RequestParam Long recordId,
-                             @ApiParam(value = "上传文件", required = true) @RequestParam MultipartFile file,
-                             @ApiParam(value = "后缀名", required = true) @RequestParam String suffix,
-                             @ApiParam(value = "md5", required = true) @RequestParam String md5) {
+            @ApiParam(value = "上传文件", required = true) @RequestParam MultipartFile file,
+            @ApiParam(value = "后缀名", required = true) @RequestParam String suffix,
+            @ApiParam(value = "md5", required = true) @RequestParam String md5) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         return ResultUtil.ok(teExamService.fileUpload(teStudent.getId(), recordId, file, suffix, md5));
     }
 
     @ApiOperation(value = "断点恢复")
     @RequestMapping(value = "/resume", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result resume(@RequestBody ResumeParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + teStudent.getId();
@@ -301,7 +306,7 @@ public class TEExamController {
 
     @ApiOperation(value = "结束考试")
     @RequestMapping(value = "/finish", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result finish(@RequestBody FinishParamBean param) {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + teStudent.getId();
@@ -319,7 +324,8 @@ public class TEExamController {
             if (param.getDurationSeconds() == null) {
                 throw new BusinessException("总用时秒数不能为空");
             }
-            return ResultUtil.ok(teExamService.finish(teStudent.getId(), param.getRecordId(), param.getType(), param.getDurationSeconds()));
+            return ResultUtil.ok(teExamService
+                    .finish(teStudent.getId(), param.getRecordId(), param.getType(), param.getDurationSeconds()));
         } finally {
             redisUtil.releaseLock(lockKey);
         }
@@ -327,7 +333,7 @@ public class TEExamController {
 
     @ApiOperation(value = "查询交卷结果")
     @RequestMapping(value = "/result", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result result(@RequestBody ResultParamBean param) {
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
             throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
@@ -337,7 +343,9 @@ public class TEExamController {
             TOeExamRecord tOeExamRecord = tOeExamRecordService.getById(param.getRecordId());
             examRecordStatusEnum = tOeExamRecord.getStatus();
         }
-        if (Objects.nonNull(examRecordStatusEnum) && (!Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects.equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
+        if (Objects.nonNull(examRecordStatusEnum) && (
+                !Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects
+                        .equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
             throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
         }
         return ResultUtil.ok(teExamService.result(param.getRecordId()));
@@ -345,13 +353,15 @@ public class TEExamController {
 
     @ApiOperation(value = "获取试卷下载信息")
     @RequestMapping(value = "/paper/download", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result paperDownload(@RequestBody ResultParamBean param) throws IOException {
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
             throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
         }
         ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(param.getRecordId());
-        if (Objects.nonNull(examRecordStatusEnum) && (!Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects.equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
+        if (Objects.nonNull(examRecordStatusEnum) && (
+                !Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects
+                        .equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
             throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
         }
         return ResultUtil.ok(teExamService.paperDownload(param.getRecordId()));
@@ -359,16 +369,20 @@ public class TEExamController {
 
     @ApiOperation(value = "试卷下载成功通知")
     @RequestMapping(value = "/paper/downloaded", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result paperDownloaded(@RequestBody ResultParamBean param) {
         if (Objects.isNull(param.getRecordId()) || Objects.equals(param.getRecordId(), "")) {
             throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
         }
         ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(param.getRecordId());
-        if (Objects.nonNull(examRecordStatusEnum) && (!Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects.equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
+        if (Objects.nonNull(examRecordStatusEnum) && (
+                !Objects.equals(ExamRecordStatusEnum.FIRST_PREPARE, examRecordStatusEnum) && !Objects
+                        .equals(ExamRecordStatusEnum.RESUME_PREPARE, examRecordStatusEnum))) {
             throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
         }
-        Integer paperDownload = Objects.isNull(ExamRecordCacheUtil.getPaperDownload(param.getRecordId())) ? 1 : ExamRecordCacheUtil.getPaperDownload(param.getRecordId());
+        Integer paperDownload = Objects.isNull(ExamRecordCacheUtil.getPaperDownload(param.getRecordId())) ?
+                1 :
+                ExamRecordCacheUtil.getPaperDownload(param.getRecordId());
         if (paperDownload.intValue() == 1) {
             ExamRecordCacheUtil.setPaperDownload(param.getRecordId(), 0);
             tOeExamRecordService.sendExamRecordDataSaveMq(param.getRecordId(), System.currentTimeMillis());
@@ -378,7 +392,7 @@ public class TEExamController {
 
     @ApiOperation(value = "查询考试状态")
     @RequestMapping(value = "/status", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "试卷信息")})
+    @ApiResponses({ @ApiResponse(code = 200, message = "试卷信息") })
     public Result status(@RequestBody ResultParamBean param) {
         if (Objects.isNull(param)) {
             throw new BusinessException(ExceptionResultEnum.PARAMS_ILLEGALITY);

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

@@ -39,6 +39,8 @@ import com.qmth.themis.common.util.ResultUtil;
 import com.qmth.themis.exam.config.ExamConstant;
 import io.swagger.annotations.*;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -64,6 +66,8 @@ import java.util.Objects;
 @RequestMapping("/${prefix.url.exam}/student")
 public class TEStudentController {
 
+    private final static Logger log = LoggerFactory.getLogger(TEStudentController.class);
+
     @Resource
     TEStudentService teStudentService;
 
@@ -105,12 +109,12 @@ public class TEStudentController {
 
     @ApiOperation(value = "学生登录接口")
     @RequestMapping(value = "/login", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "学生信息", response = TEExamResultDto.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "学生信息", response = TEExamResultDto.class) })
     public Result login(
-            @ApiJsonObject(name = "loginStudent", value = {@ApiJsonProperty(key = "identity", description = "证件号"),
+            @ApiJsonObject(name = "loginStudent", value = { @ApiJsonProperty(key = "identity", description = "证件号"),
                     @ApiJsonProperty(key = "password", description = "密码"),
                     @ApiJsonProperty(key = "orgId", type = "long", example = "1", description = "机构id"),
-                    @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id")}) @ApiParam(value = "学生信息", required = true) @RequestBody Map<String, Object> mapParameter)
+                    @ApiJsonProperty(key = "examId", type = "long", example = "1", description = "批次id") }) @ApiParam(value = "学生信息", required = true) @RequestBody Map<String, Object> mapParameter)
             throws NoSuchAlgorithmException {
         if (Objects.isNull(mapParameter)) {
             throw new BusinessException(ExceptionResultEnum.STUDENT_IS_NULL);
@@ -158,30 +162,29 @@ public class TEStudentController {
             user.setBasePhotoPath(
                     ossUtil.getAliYunOssPublicDomain().getPublicUrl() + File.separator + user.getBasePhotoPath());
         }
-//        String loginPassword = AesUtil.decryptCs7(password, Constants.AES_RULE);
-//        密码错误
-//        String aesPassword = AesUtil.decryptCs7(user.getPassword(), Constants.AES_RULE);
+        //        String loginPassword = AesUtil.decryptCs7(password, Constants.AES_RULE);
+        //        密码错误
+        //        String aesPassword = AesUtil.decryptCs7(user.getPassword(), Constants.AES_RULE);
         if (!Objects.equals(password, user.getPassword())) {
             throw new BusinessException(ExceptionResultEnum.PASSWORD_ERROR);
         }
-        //判断是否有正在考试的
-        Long unFinishedRecordId = Objects.nonNull(ExamingDataCacheUtil.getUnFinishedRecordId(user.getId())) ? ExamingDataCacheUtil.getUnFinishedRecordId(user.getId()) : null;
-        if (Objects.isNull(unFinishedRecordId)) {
+        String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + user.getId();
+        redisUtil.waitLock(lockKey, SystemConstant.REDIS_LOCK_STUDENT_TIME_OUT);
+        try {
+            //判断是否有正在考试的记录
             TEStudentCacheDto teStudentCacheDto = ExamingDataCacheUtil.getStudentExaming(user.getId());
-            if (Objects.nonNull(teStudentCacheDto) && Objects.nonNull(teStudentCacheDto.getExamingRecordId())) {
-                unFinishedRecordId = teStudentCacheDto.getExamingRecordId();
-            }
-        }
-
-        if (Objects.nonNull(unFinishedRecordId)) {
-            WebsocketStatusEnum sta = ExamRecordCacheUtil.getClientWebsocketStatus(unFinishedRecordId);
-            ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(unFinishedRecordId);
-            if (WebsocketStatusEnum.ON_LINE.equals(sta) && (Objects.nonNull(status) && !Objects
-                    .equals(status, ExamRecordStatusEnum.FIRST_PREPARE))) {
-                throw new BusinessException(ExceptionResultEnum.STUDENT_NOT_ALLOW_LOGIN);
+            Long examingRecordId = teStudentCacheDto != null ? teStudentCacheDto.getExamingRecordId() : null;
+            if (Objects.nonNull(examingRecordId)) {
+                WebsocketStatusEnum sta = ExamRecordCacheUtil.getClientWebsocketStatus(examingRecordId);
+                //有正在考试的记录,且考生端websocket在线则不允许登陆
+                if (WebsocketStatusEnum.ON_LINE.equals(sta)) {
+                    throw new BusinessException(ExceptionResultEnum.STUDENT_NOT_ALLOW_LOGIN);
+                }
             }
+            return userLoginCommon(user, examId, orgId);
+        } finally {
+            redisUtil.releaseLock(lockKey);
         }
-        return userLoginCommon(user, examId, orgId);
     }
 
     /**
@@ -195,7 +198,7 @@ public class TEStudentController {
      */
     private Result userLoginCommon(TEStudent teStudent, Long examId, Long orgId) throws NoSuchAlgorithmException {
         //停用
-        if (teStudent.getEnable().intValue() == 0) {
+        if (teStudent.getEnable() == 0) {
             throw new BusinessException(ExceptionResultEnum.STUDENT_ENABLE);
         }
         Platform platform = ServletUtil.getRequestPlatform();
@@ -222,7 +225,8 @@ public class TEStudentController {
                 ServletUtil.getRequest().getLocalAddr(), token, expireTime.getDate().getTime());
         redisUtil.setUserSession(sessionId, tbSession, expireTime.getExpireSeconds());
         //mq发送消息start
-        MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), platform.name(), SystemOperationEnum.SESSION, sessionId);
+        MqDto mqDto = new MqDto(mqUtil.getMqGroupDomain().getTopic(), platform.name(), SystemOperationEnum.SESSION,
+                sessionId);
         mqDtoService.assembleSendOneWayMsg(mqDto);
         MqDto mqDtoLog = new MqDto(mqUtil.getMqGroupDomain().getTopic(), MqTagEnum.STUDENT.name(),
                 SystemOperationEnum.LOGIN, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()),
@@ -230,17 +234,19 @@ public class TEStudentController {
         mqDtoService.assembleSendOneWayMsg(mqDtoLog);
         //mq发送消息end
         //测试
-//        String test = SignatureInfo.build(SignatureType.TOKEN, sessionId, token);
+        //        String test = SignatureInfo.build(SignatureType.TOKEN, sessionId, token);
         Map<String, Object> map = new HashMap<>();
         //获取未完考试
-        if (Objects.isNull(ExamingDataCacheUtil.getUnFinishedRecordId(teStudent.getId()))) {
+        Long recordId = ExamingDataCacheUtil.getUnFinishedRecordId(teStudent.getId());
+        if (Objects.isNull(recordId)) {
             map = this.getWaitList(teStudent.getId(), examId, orgId, map);
         } else {
-            Long recordId = ExamingDataCacheUtil.getUnFinishedRecordId(teStudent.getId());
             //获取考试记录缓存
             ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
             if (Objects.isNull(status)) {
-                map = this.getWaitList(teStudent.getId(), examId, orgId, map);
+                //map = this.getWaitList(teStudent.getId(), examId, orgId, map);
+                log.error("ExamRecordStatus in cache is null, recordId=" + recordId);
+                throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
             } else {
                 Long ecExamId = ExamRecordCacheUtil.getExamId(recordId);
                 Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
@@ -263,7 +269,8 @@ public class TEStudentController {
                         map = this.getWaitList(teStudent.getId(), examId, orgId, map);
                     } else {
                         ExamUnFinishBean examUnFinishBean = this
-                                .unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean, examStudentId);
+                                .unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean,
+                                        examStudentId);
                         map.put("unFinished", examUnFinishBean);
                     }
                 } else {
@@ -275,7 +282,7 @@ public class TEStudentController {
         TEConfig teConfig = teConfigService.getGlobalConfig();
         map.put(SystemConstant.ACCESS_TOKEN, token);
         map.put(SystemConstant.GLOBAL, teConfig);
-//        map.put(SystemConstant.ACCESS_TOKEN, test);
+        //        map.put(SystemConstant.ACCESS_TOKEN, test);
         map.put(SystemConstant.STUDENT_ACCOUNT, teStudent);
         map.put(SystemConstant.SESSION_ID, sessionId);
         return ResultUtil.ok(map);
@@ -283,7 +290,7 @@ public class TEStudentController {
 
     @ApiOperation(value = "登出接口")
     @RequestMapping(value = "/logout", method = RequestMethod.POST)
-    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
+    @ApiResponses({ @ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class) })
     public Result logout() throws NoSuchAlgorithmException {
         TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
         TBSession tbSession = (TBSession) ServletUtil.getRequestSession();
@@ -326,11 +333,11 @@ public class TEStudentController {
      * @return
      */
     private ExamUnFinishBean unFinishCommon(Long recordId, ExamCacheBean ec, ExamStudentCacheBean examStudentCacheBean,
-                                            ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
+            ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
         ExamCourseCacheBean examCourseCacheBean = teExamCourseService
                 .getExamCourseCacheBean(ec.getId(), examStudentCacheBean.getCourseCode());
-        TEExamActivityWaitDto teExamActivityWaitDto = new TEExamActivityWaitDto(ec, examActivityCacheBean, examStudentCacheBean,
-                examStudentId, examCourseCacheBean, ExamRecordCacheUtil.getStartTime(recordId),
+        TEExamActivityWaitDto teExamActivityWaitDto = new TEExamActivityWaitDto(ec, examActivityCacheBean,
+                examStudentCacheBean, examStudentId, examCourseCacheBean, ExamRecordCacheUtil.getStartTime(recordId),
                 ExamRecordCacheUtil.getEndTime(recordId), ExamRecordCacheUtil.getOpeningSeconds(recordId),
                 ExamRecordCacheUtil.getMinDurationSeconds(recordId),
                 ExamRecordCacheUtil.getMaxDurationSeconds(recordId), ExamRecordCacheUtil.getForceFinish(recordId));