Selaa lähdekoodia

新增学生视频监考回放查询

wangliang 2 vuotta sitten
vanhempi
commit
28f89a3ff7
27 muutettua tiedostoa jossa 602 lisäystä ja 196 poistoa
  1. 10 32
      themis-admin/src/main/java/com/qmth/themis/admin/api/SysController.java
  2. 1 1
      themis-admin/src/main/java/com/qmth/themis/admin/api/TEExamActivityController.java
  3. 6 60
      themis-admin/src/main/java/com/qmth/themis/admin/api/TENotifyController.java
  4. 13 2
      themis-admin/src/main/java/com/qmth/themis/admin/api/TEStudentController.java
  5. 2 2
      themis-admin/src/main/resources/application-dev.properties
  6. 1 2
      themis-admin/src/main/resources/application-main-temp.properties
  7. 10 10
      themis-business/src/main/java/com/qmth/themis/business/cache/ExamRecordCacheUtil.java
  8. 17 6
      themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java
  9. 11 1
      themis-business/src/main/java/com/qmth/themis/business/dao/TEStudentMapper.java
  10. 8 0
      themis-business/src/main/java/com/qmth/themis/business/dao/TOeExamRecordMapper.java
  11. 17 18
      themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordDto.java
  12. 165 0
      themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordVideoDto.java
  13. 61 0
      themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordVideoMessageDto.java
  14. 3 3
      themis-business/src/main/java/com/qmth/themis/business/entity/TEAudio.java
  15. 62 1
      themis-business/src/main/java/com/qmth/themis/business/entity/TMTencentVideoMessage.java
  16. 9 9
      themis-business/src/main/java/com/qmth/themis/business/entity/TOeExamRecord.java
  17. 1 1
      themis-business/src/main/java/com/qmth/themis/business/enums/ExamRecordFieldEnum.java
  18. 13 0
      themis-business/src/main/java/com/qmth/themis/business/service/TEStudentService.java
  19. 8 0
      themis-business/src/main/java/com/qmth/themis/business/service/TOeExamRecordService.java
  20. 55 13
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TEStudentServiceImpl.java
  21. 58 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java
  22. 29 2
      themis-business/src/main/resources/mapper/TEStudentMapper.xml
  23. 17 0
      themis-business/src/main/resources/mapper/TOeExamRecordMapper.xml
  24. 1 1
      themis-exam/src/main/resources/application-dev.properties
  25. 13 29
      themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java
  26. 6 2
      themis-task/src/main/resources/application-dev.properties
  27. 5 1
      themis-task/src/main/resources/application-main-temp.properties

+ 10 - 32
themis-admin/src/main/java/com/qmth/themis/admin/api/SysController.java

@@ -1,6 +1,6 @@
 package com.qmth.themis.admin.api;
 
-import com.alibaba.fastjson.JSONArray;
+import cn.hutool.core.date.DateUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.common.reflect.TypeToken;
@@ -435,10 +435,7 @@ public class SysController {
     @ApiResponses({@ApiResponse(code = 200, message = "腾讯视频接口", response = Result.class)})
     @Transactional
     public Result getTencentVideo(@ApiParam(value = "考试批次id", required = true) @RequestParam Long examId) {
-        QueryWrapper<TOeExamRecord> tOeExamRecordQueryWrapper = new QueryWrapper<>();
-        tOeExamRecordQueryWrapper.lambda().eq(TOeExamRecord::getExamId, examId)
-                .isNull(TOeExamRecord::getTencentVideoUrl);
-        List<TOeExamRecord> tOeExamRecordList = tOeExamRecordService.list(tOeExamRecordQueryWrapper);
+        List<TOeExamRecord> tOeExamRecordList = tOeExamRecordService.findExamRecordByNotVideoUrl(examId);
 
         if (!CollectionUtils.isEmpty(tOeExamRecordList)) {
             for (TOeExamRecord t : tOeExamRecordList) {
@@ -446,33 +443,21 @@ public class SysController {
                 if (!CollectionUtils.isEmpty(resultMap)) {
                     SearchMediaResponse response = (SearchMediaResponse) resultMap.get("object");
                     if (Objects.nonNull(response) && response.getTotalCount().intValue() > 0) {
-                        Map<String, String> map = new HashMap<>();
-                        JSONArray jsonArray = null;
-                        int countDb = 0;
-                        if (Objects.nonNull(t.getTencentVideoUrl())) {
-                            jsonArray = JSONArray.parseArray(t.getTencentVideoUrl());
-                            countDb = jsonArray.size();
-                            for (int i = 0; i < countDb; i++) {
-                                JSONObject jsonObject = (JSONObject) jsonArray.get(i);
-                                String videoSource = SystemConstant.getMonitorRecordStreamId((String) jsonObject.get(SystemConstant.VIDEO_SOURCE));
-                                map.put(videoSource, videoSource);
-                            }
-                        } else {
-                            jsonArray = new JSONArray();
-                        }
                         MediaInfo[] mediaInfos = response.getMediaInfoSet();
                         for (int i = 0; i < mediaInfos.length; i++) {
                             MediaBasicInfo mediaBasicInfo = mediaInfos[i].getBasicInfo();
-                            String videoSource = SystemConstant.getMonitorRecordStreamId(mediaBasicInfo.getName());
+                            String mediaBasicName = mediaBasicInfo.getName();
+                            String videoSource = SystemConstant.getMonitorRecordStreamId(mediaBasicName);
+                            String[] timeArrays = mediaBasicName.split("_");
+                            Long startTime = DateUtil.parse(timeArrays[6], SystemConstant.VIDEO_TIME_FORMAT).getTime();
+                            Long endTime = DateUtil.parse(timeArrays[7], SystemConstant.VIDEO_TIME_FORMAT).getTime();
                             boolean lock = false;
                             for (int y = 0; y < SystemConstant.CONSUME_MESSAGE_BATCH_MAX_SIZE; y++) {
                                 lock = redisUtil.lock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource, SystemConstant.REDIS_LOCK_TENCENT_VIDEO_TIME_OUT);
-                                if (lock && Objects.isNull(map.get(videoSource))) {
+                                if (lock) {
                                     try {
-                                        JSONObject jsonObject = new JSONObject();
-                                        jsonObject.put(SystemConstant.VIDEO_SOURCE, videoSource);
-                                        jsonObject.put(SystemConstant.VIDEO_URL, mediaBasicInfo.getMediaUrl());
-                                        jsonArray.add(jsonObject);
+                                        TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(response.getRequestId(), JacksonUtil.parseJson(response), t.getId(), videoSource, startTime, endTime, mediaBasicInfo.getMediaUrl());
+                                        tencentVideoMessageService.save(tencentVideoMessage);
                                         break;
                                     } finally {
                                         redisUtil.releaseLock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource);
@@ -486,13 +471,6 @@ public class SysController {
                                 }
                             }
                         }
-                        if (Objects.nonNull(jsonArray) && jsonArray.size() > 0 && countDb != jsonArray.size()) {
-                            t.setTencentVideoUrl(jsonArray.toJSONString());
-                            tOeExamRecordService.updateById(t);
-
-                            TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(response.getRequestId(), JacksonUtil.parseJson(response));
-                            tencentVideoMessageService.save(tencentVideoMessage);
-                        }
                     }
                 }
             }

+ 1 - 1
themis-admin/src/main/java/com/qmth/themis/admin/api/TEExamActivityController.java

@@ -133,7 +133,7 @@ public class TEExamActivityController {
         TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
         TEAudio teAudio = teAudioService.getById(audioId);
         Optional.ofNullable(teAudio).orElseThrow(() -> new BusinessException("语音信息不存在"));
-        teAudio.setEnable(Objects.nonNull(enable) && enable ? 1 : 0);
+        teAudio.setEnable(enable);
         teAudio.updateInfo(tbUser.getId());
         teAudioService.updateById(teAudio);
         return ResultUtil.ok(true);

+ 6 - 60
themis-admin/src/main/java/com/qmth/themis/admin/api/TENotifyController.java

@@ -1,6 +1,6 @@
 package com.qmth.themis.admin.api;
 
-import com.alibaba.fastjson.JSONArray;
+import cn.hutool.core.date.DateUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.qmth.themis.admin.config.DictionaryConfig;
 import com.qmth.themis.business.bean.admin.TencentEventBean;
@@ -30,6 +30,7 @@ import com.qmth.themis.common.util.Result;
 import com.qmth.themis.common.util.ResultUtil;
 import com.tencentcloudapi.common.Sign;
 import io.swagger.annotations.*;
+import org.apache.http.client.utils.DateUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.annotation.Transactional;
@@ -104,6 +105,8 @@ public class TENotifyController {
         //说明是录制视频回调成功
         if (Objects.nonNull(eventType) && Objects.equals(eventType, "100")) {
             String streamId = String.valueOf(jsonObject.get("stream_id"));
+            Long startTime = jsonObject.getLong("start_time");
+            Long endTime = jsonObject.getLong("end_time");
             String videoSource = SystemConstant.getMonitorRecordStreamId(streamId);
             boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource, SystemConstant.REDIS_LOCK_TENCENT_VIDEO_TIME_OUT);
             if (lock) {
@@ -112,34 +115,8 @@ public class TENotifyController {
                     if (Objects.nonNull(streamId)) {
                         String[] str = streamId.split("_");
                         recordId = Long.parseLong(str[2]);
-                        String tencentVideoUrl = ExamRecordCacheUtil.getTencentVideoUrl(recordId);
-                        if (Objects.nonNull(tencentVideoUrl)) {//说明缓存还在
-                            JSONArray jsonArrayDb = JSONArray.parseArray(tencentVideoUrl);
-                            int count = jsonArrayDb.size();
-                            JSONArray jsonArray = this.getVideoUrl(tencentVideoUrl, videoSource, jsonObject);
-                            if (Objects.nonNull(jsonArray) && jsonArray.size() > 0 && count != jsonArray.size()) {
-                                if (Objects.nonNull(ExamRecordCacheUtil.getTencentVideoUrl(recordId)) && Objects.equals(tencentVideoUrl, ExamRecordCacheUtil.getTencentVideoUrl(recordId))) {
-                                    ExamRecordCacheUtil.setTencentVideoUrl(recordId, jsonArray.toJSONString());
-                                    tOeExamRecordService.sendExamRecordDataSaveMqNotIp(recordId, System.currentTimeMillis());
-                                }
-                            }
-                        } else {
-                            TOeExamRecord tOeExamRecord = SystemConstant.getExamRecord(recordId);
-                            int count = 0;
-                            JSONArray jsonArrayDb = null;
-                            if (Objects.nonNull(tOeExamRecord.getTencentVideoUrl())) {
-                                jsonArrayDb = JSONArray.parseArray(tOeExamRecord.getTencentVideoUrl());
-                                count = jsonArrayDb.size();
-                            }
-                            JSONArray jsonArray = this.getVideoUrl(tOeExamRecord.getTencentVideoUrl(), videoSource, jsonObject);
-                            if (Objects.nonNull(jsonArray) && jsonArray.size() > 0 && count != jsonArray.size()) {
-                                tOeExamRecord.setTencentVideoUrl(jsonArray.toJSONString());
-                                tOeExamRecordService.updateById(tOeExamRecord);
-
-                                TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(SystemConstant.getUuid(), jsonObject.toJSONString());
-                                tencentVideoMessageService.save(tencentVideoMessage);
-                            }
-                        }
+                        TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(SystemConstant.getUuid(), jsonObject.toJSONString(), recordId, videoSource, startTime * 1000, endTime * 1000, String.valueOf(jsonObject.get("video_url")));
+                        tencentVideoMessageService.save(tencentVideoMessage);
                     }
                 } finally {
                     redisUtil.releaseLock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource);
@@ -149,37 +126,6 @@ public class TENotifyController {
         return ResultUtil.ok(true);
     }
 
-    /**
-     * 获取视频源地址
-     *
-     * @param tencentVideoUrl
-     * @param videoSource
-     * @param jsonObject
-     * @return
-     */
-    private JSONArray getVideoUrl(String tencentVideoUrl, String videoSource, JSONObject jsonObject) {
-        Map<String, String> map = new HashMap<>();
-        JSONArray jsonArray = null;
-        if (Objects.isNull(tencentVideoUrl)) {
-            jsonArray = new JSONArray();
-        } else {
-            jsonArray = JSONArray.parseArray(tencentVideoUrl);
-        }
-        for (int i = 0; i < jsonArray.size(); i++) {
-            JSONObject json = (JSONObject) jsonArray.get(i);
-            String videoSourceTemp = SystemConstant.getMonitorRecordStreamId((String) json.get(SystemConstant.VIDEO_SOURCE));
-            map.put(videoSourceTemp, videoSourceTemp);
-        }
-        if (Objects.isNull(map.get(videoSource))) {
-            String videoUrl = String.valueOf(jsonObject.get("video_url"));
-            JSONObject json = new JSONObject();
-            json.put(SystemConstant.VIDEO_SOURCE, videoSource);
-            json.put(SystemConstant.VIDEO_URL, videoUrl);
-            jsonArray.add(json);
-        }
-        return jsonArray;
-    }
-
     @ApiOperation(value = "腾讯云状态事件接口回调")
     @RequestMapping(value = "/status/tencent", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "结果信息")})

+ 13 - 2
themis-admin/src/main/java/com/qmth/themis/admin/api/TEStudentController.java

@@ -10,6 +10,7 @@ import com.qmth.themis.business.dto.response.TEStudentDto;
 import com.qmth.themis.business.entity.TBUser;
 import com.qmth.themis.business.entity.TEStudent;
 import com.qmth.themis.business.entity.TOeExamRecord;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
 import com.qmth.themis.business.service.CacheService;
 import com.qmth.themis.business.service.TEStudentService;
 import com.qmth.themis.business.util.OssUtil;
@@ -27,7 +28,6 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
-import javax.validation.Valid;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
 import java.util.Map;
@@ -125,7 +125,7 @@ public class TEStudentController {
     @RequestMapping(value = "/exam_record/query", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "学生考试记录信息", response = TOeExamRecord.class)})
     public Result examRecordQuery(@ApiParam(value = "学生ID", required = true) @RequestParam Long studentId,
-                                  @ApiParam(value = "考试id", required = false) @RequestParam(required = false) Long examId,
+                                  @ApiParam(value = "考试id") @RequestParam(required = false) Long examId,
                                   @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
                                   @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
         if (Objects.isNull(studentId) || Objects.equals(studentId, "")) {
@@ -134,6 +134,17 @@ public class TEStudentController {
         return ResultUtil.ok(teStudentService.studentExamRecordQuery(new Page<>(pageNumber, pageSize), studentId, examId));
     }
 
+    @ApiOperation(value = "学生视频监考回放查询接口")
+    @RequestMapping(value = "/exam_record/video/query", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "学生视频监考回放信息", response = TOeExamRecord.class)})
+    public Result examRecordVideoQuery(@ApiParam(value = "考试记录id", required = true) @RequestParam(required = true) Long examRecordId,
+                                       @ApiParam(value = "视频源") @RequestParam(required = false) MonitorVideoSourceEnum monitorVideoSource,
+                                       @ApiParam(value = "是否查询轨迹日志") @RequestParam(required = false) Boolean log,
+                                       @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
+                                       @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
+        return ResultUtil.ok(teStudentService.examRecordVideoQuery(new Page<>(pageNumber, pageSize), examRecordId, monitorVideoSource, log));
+    }
+
     @ApiOperation(value = "底照上传")
     @RequestMapping(value = "/base_photo/upload", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "结果信息")})

+ 2 - 2
themis-admin/src/main/resources/application-dev.properties

@@ -142,7 +142,7 @@ tencentyun.sdk.secretId=AKIDKUO2PVLuCDxQcW9VaOuA8pFOGq9BwQdZ
 tencentyun.sdk.secretKey=g5D4dwxhByXrvjGWVDfqkPqzrSmqd9OM
 tencentyun.sdk.vodAppId=1500002365
 tencentyun.sdk.callbackPwd=123456
-tencentyun.sdk.callbackTime=10m
+tencentyun.sdk.callbackTime=2m
 tencentyun.sdk.trtcQueryUrl=trtc.tencentcloudapi.com
 tencentyun.sdk.trtcRegion=ap-guangzhou
 
@@ -157,7 +157,7 @@ sys.config.adminLogoUrl=http://qmth-test.oss-cn-shenzhen.aliyuncs.com/frontend/w
 spring.resources.static-locations=file:${sys.config.serverUpload},classpath:/META-INF/resources/,classpath:/resources/
 
 #\u4E91\u9605\u5377\u540C\u6B65\u914D\u7F6E
-yun.mark.url=http://localhost:8081
+yun.mark.url=https://epcc.markingcloud.com/admin/home
 yun.mark.studentScoreApi=/api/exam/student/score
 
 #============================================================================

+ 1 - 2
themis-admin/src/main/resources/application-main-temp.properties

@@ -147,6 +147,5 @@ no.auth.urls=/webjars/**,/druid/**,/swagger-ui.html,/doc.html,/swagger-resources
 
 common.system.urls=/api/admin/sys/getMenu,/api/admin/user/logout,/api/admin/sys/env,/api/admin/sys/file/upload,/api/admin/sys/file/download,/api/admin/sys/org/query,/api/admin/sys/role/query,/api/admin/sys/examActivity/query,/api/admin/sys/exam/query,/api/admin/sys/examRoom/query,/api/admin/sys/exam/privilegeQuery,/api/admin/student/photo/upload,/api/admin/sys/getPlayUrls,/api/admin/sys/exam/finish/query,/api/admin/sys/get_tencent_video
 
-
-yun.mark.url=http://localhost:8081
+yun.mark.url=https://epcc.markingcloud.com/admin/home
 yun.mark.studentScoreApi=/api/exam/student/score

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

@@ -921,12 +921,12 @@ public class ExamRecordCacheUtil {
         }
     }
 
-    public static void setTencentVideoUrl(Long recordId, String tencentVideoUrl) {
-        if (Objects.nonNull(getId(recordId))) {
-            redisUtil.set(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.tencent_video_url.getCode(),
-                    tencentVideoUrl);
-        }
-    }
+//    public static void setTencentVideoUrl(Long recordId, String tencentVideoUrl) {
+//        if (Objects.nonNull(getId(recordId))) {
+//            redisUtil.set(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.tencent_video_url.getCode(),
+//                    tencentVideoUrl);
+//        }
+//    }
 
     public static void setInProcessRealnessVerifyStatus(Long recordId, Integer inProcessRealnessVerifyStatus) {
         if (Objects.nonNull(getId(recordId))) {
@@ -1009,10 +1009,10 @@ public class ExamRecordCacheUtil {
                 .get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.monitor_record.getCode());
     }
 
-    public static String getTencentVideoUrl(Long recordId) {
-        return (String) redisUtil
-                .get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.tencent_video_url.getCode());
-    }
+//    public static String getTencentVideoUrl(Long recordId) {
+//        return (String) redisUtil
+//                .get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.tencent_video_url.getCode());
+//    }
 
     public static void setUpdateTime(Long recordId, Long date) {
         if (Objects.nonNull(getId(recordId))) {

+ 17 - 6
themis-business/src/main/java/com/qmth/themis/business/constant/SystemConstant.java

@@ -44,6 +44,8 @@ public class SystemConstant {
 
     public static final String PATTERN_NUMBER = "^[0-9]*$";
 
+    public static final String VIDEO_TIME_FORMAT = "yyyy-MM-dd-HH-mm-ss";
+
     /**
      * 鉴权
      */
@@ -589,6 +591,19 @@ public class SystemConstant {
      * @return
      */
     public static String getMonitorRecordStreamId(String videoSource) {
+        MonitorVideoSourceEnum monitorVideoSourceEnum = getMonitorRecordVideoSource(videoSource);
+        return videoSource.substring(0,
+                videoSource.indexOf(monitorVideoSourceEnum.name().toLowerCase()) + monitorVideoSourceEnum.name()
+                        .length());
+    }
+
+    /**
+     * 获取监控源
+     *
+     * @param videoSource
+     * @return
+     */
+    public static MonitorVideoSourceEnum getMonitorRecordVideoSource(String videoSource) {
         MonitorVideoSourceEnum monitorVideoSourceEnum = null;
         if (videoSource.contains(MonitorVideoSourceEnum.CLIENT_CAMERA.name().toLowerCase())) {
             monitorVideoSourceEnum = MonitorVideoSourceEnum.CLIENT_CAMERA;
@@ -597,9 +612,7 @@ 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 monitorVideoSourceEnum;
     }
 
     /**
@@ -708,9 +721,7 @@ public class SystemConstant {
     public static TOeExamRecord getExamRecord(Long recordId) {
         TOeExamRecordService tOeExamRecordService = SpringContextHolder.getBean(TOeExamRecordService.class);
         TOeExamRecord tOeExamRecord = tOeExamRecordService.getById(recordId);
-        if (Objects.isNull(tOeExamRecord)) {
-            throw new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_RECORD);
-        }
+        Optional.ofNullable(tOeExamRecord).orElseThrow(() -> new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_RECORD));
         return tOeExamRecord;
     }
 

+ 11 - 1
themis-business/src/main/java/com/qmth/themis/business/dao/TEStudentMapper.java

@@ -1,10 +1,10 @@
 package com.qmth.themis.business.dao;
 
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.qmth.themis.business.base.CustomBaseMapper;
 import com.qmth.themis.business.dto.response.TEStudentDto;
 import com.qmth.themis.business.dto.response.TEStudentExamRecordDto;
+import com.qmth.themis.business.dto.response.TEStudentExamRecordVideoMessageDto;
 import com.qmth.themis.business.entity.TEStudent;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -43,4 +43,14 @@ public interface TEStudentMapper extends CustomBaseMapper<TEStudent> {
      * @return
      */
     public IPage<TEStudentExamRecordDto> studentExamRecordQuery(IPage<Map> iPage, @Param("studentId") Long studentId, @Param("examId") Long examId);
+
+    /**
+     * 视频监考回放查询接口
+     *
+     * @param iPage
+     * @param examRecordId
+     * @param monitorVideoSource
+     * @return
+     */
+    public IPage<TEStudentExamRecordVideoMessageDto> examRecordVideoQuery(IPage<Map> iPage, @Param("examRecordId") Long examRecordId, @Param("monitorVideoSource") String monitorVideoSource);
 }

+ 8 - 0
themis-business/src/main/java/com/qmth/themis/business/dao/TOeExamRecordMapper.java

@@ -504,4 +504,12 @@ public interface TOeExamRecordMapper extends BaseMapper<TOeExamRecord> {
      * @return
      */
     public List<TimeOnlineDataResult> getTimeOnlineData(@Param("orgId") Long orgId, @Param("timeData") List<String> timeData, @Param("currentTime") Long currentTime, @Param("minusTime") Long minusTime);
+
+    /**
+     * 查询没有视频地址的考试记录
+     *
+     * @param examId
+     * @return
+     */
+    public List<TOeExamRecord> findExamRecordByNotVideoUrl(@Param("examId") Long examId);
 }

+ 17 - 18
themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordDto.java

@@ -6,7 +6,6 @@ import com.qmth.themis.business.enums.ExamRecordStatusEnum;
 import io.swagger.annotations.ApiModelProperty;
 
 import java.io.Serializable;
-import java.util.List;
 
 /**
  * @Description: 学生考试记录 dto
@@ -50,11 +49,8 @@ public class TEStudentExamRecordDto implements Serializable {
     @ApiModelProperty(name = "考试状态")
     private ExamRecordStatusEnum status;//考试状态
 
-    @ApiModelProperty(name = "转录视频地址源")
-    private String tencentVideoUrl;
-
-    @ApiModelProperty(name = "转录视频地址")
-    private List<TEStudentMonitorRecordDto> monitorRecord;
+    @ApiModelProperty(name = "视频数量")
+    private Integer videoCount = 0;
 
     @ApiModelProperty(name = "首次开考时间")
     private Long firstStartTime;
@@ -62,6 +58,17 @@ public class TEStudentExamRecordDto implements Serializable {
     @ApiModelProperty(name = "交卷时间")
     private Long finishTime;
 
+    @ApiModelProperty(name = "开启监控的视频源")
+    private String monitorVideoSource;
+
+    public String getMonitorVideoSource() {
+        return monitorVideoSource;
+    }
+
+    public void setMonitorVideoSource(String monitorVideoSource) {
+        this.monitorVideoSource = monitorVideoSource;
+    }
+
     public Long getFinishTime() {
         return finishTime;
     }
@@ -78,20 +85,12 @@ public class TEStudentExamRecordDto implements Serializable {
         this.firstStartTime = firstStartTime;
     }
 
-    public List<TEStudentMonitorRecordDto> getMonitorRecord() {
-        return monitorRecord;
-    }
-
-    public void setMonitorRecord(List<TEStudentMonitorRecordDto> monitorRecord) {
-        this.monitorRecord = monitorRecord;
-    }
-
-    public String getTencentVideoUrl() {
-        return tencentVideoUrl;
+    public Integer getVideoCount() {
+        return videoCount;
     }
 
-    public void setTencentVideoUrl(String tencentVideoUrl) {
-        this.tencentVideoUrl = tencentVideoUrl;
+    public void setVideoCount(Integer videoCount) {
+        this.videoCount = videoCount;
     }
 
     public Long getExamId() {

+ 165 - 0
themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordVideoDto.java

@@ -0,0 +1,165 @@
+package com.qmth.themis.business.dto.response;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.themis.business.entity.TEExamStudentLog;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Description: 学生视频监考回放记录 dto
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/8/3
+ */
+public class TEStudentExamRecordVideoDto implements Serializable {
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(name = "考试批次id")
+    private Long examId;//考试批次id
+
+    @ApiModelProperty(name = "考试名称")
+    private String examName;//考试名称
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(name = "考生id")
+    private Long examStudentId;//考生id
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(name = "考试记录id")
+    private Long examRecordId;//考试记录id
+
+    @ApiModelProperty(name = "科目编码")
+    private String courseCode;//科目编码
+
+    @ApiModelProperty(name = "科目名称")
+    private String courseName;//科目名称
+
+    @ApiModelProperty(name = "证件号")
+    private String identity;//证件号
+
+    @ApiModelProperty(name = "姓名")
+    private String name;//姓名
+
+    @ApiModelProperty(name = "首次开考时间")
+    private Long firstStartTime;
+
+    @ApiModelProperty(name = "考生轨迹")
+    List<TEExamStudentLog> teExamStudentLogList;
+
+    @ApiModelProperty(name = "视频集合")
+    IPage<TEStudentExamRecordVideoMessageDto> teStudentExamRecordVideoMessageIPage;
+
+    public TEStudentExamRecordVideoDto() {
+
+    }
+
+    public TEStudentExamRecordVideoDto(Long examId, String examName, Long examStudentId, Long examRecordId,
+                                       String courseCode, String courseName, String identity, String name,
+                                       Long firstStartTime, List<TEExamStudentLog> teExamStudentLogList,
+                                       IPage<TEStudentExamRecordVideoMessageDto> teStudentExamRecordVideoMessageIPage) {
+        this.examId = examId;
+        this.examName = examName;
+        this.examStudentId = examStudentId;
+        this.examRecordId = examRecordId;
+        this.courseCode = courseCode;
+        this.courseName = courseName;
+        this.identity = identity;
+        this.name = name;
+        this.firstStartTime = firstStartTime;
+        this.teExamStudentLogList = teExamStudentLogList;
+        this.teStudentExamRecordVideoMessageIPage = teStudentExamRecordVideoMessageIPage;
+    }
+
+    public List<TEExamStudentLog> getTeExamStudentLogList() {
+        return teExamStudentLogList;
+    }
+
+    public void setTeExamStudentLogList(List<TEExamStudentLog> teExamStudentLogList) {
+        this.teExamStudentLogList = teExamStudentLogList;
+    }
+
+    public IPage<TEStudentExamRecordVideoMessageDto> getTeStudentExamRecordVideoMessageIPage() {
+        return teStudentExamRecordVideoMessageIPage;
+    }
+
+    public void setTeStudentExamRecordVideoMessageIPage(IPage<TEStudentExamRecordVideoMessageDto> teStudentExamRecordVideoMessageIPage) {
+        this.teStudentExamRecordVideoMessageIPage = teStudentExamRecordVideoMessageIPage;
+    }
+
+    public Long getFirstStartTime() {
+        return firstStartTime;
+    }
+
+    public void setFirstStartTime(Long firstStartTime) {
+        this.firstStartTime = firstStartTime;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public Long getExamStudentId() {
+        return examStudentId;
+    }
+
+    public void setExamStudentId(Long examStudentId) {
+        this.examStudentId = examStudentId;
+    }
+
+    public Long getExamRecordId() {
+        return examRecordId;
+    }
+
+    public void setExamRecordId(Long examRecordId) {
+        this.examRecordId = examRecordId;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(String identity) {
+        this.identity = identity;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 61 - 0
themis-business/src/main/java/com/qmth/themis/business/dto/response/TEStudentExamRecordVideoMessageDto.java

@@ -0,0 +1,61 @@
+package com.qmth.themis.business.dto.response;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @Description: 学生视频监考回放记录 dto
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/8/3
+ */
+public class TEStudentExamRecordVideoMessageDto implements Serializable {
+
+    @ApiModelProperty(name = "视频源")
+    private String videoSource;//视频源
+
+    @ApiModelProperty(name = "开始时间")
+    private Long startTime;//开始时间
+
+    @ApiModelProperty(name = "结束时间")
+    private Long endTime;//结束时间
+
+    @ApiModelProperty(name = "视频地址")
+    private String videoUrl;//科目编码
+
+    public String getVideoSource() {
+        return Objects.nonNull(videoSource) ? SystemConstant.getMonitorRecordVideoSource(videoSource).name() : videoSource;
+    }
+
+    public void setVideoSource(String videoSource) {
+        this.videoSource = videoSource;
+    }
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
+    }
+}

+ 3 - 3
themis-business/src/main/java/com/qmth/themis/business/entity/TEAudio.java

@@ -49,7 +49,7 @@ public class TEAudio extends BaseEntity implements Serializable {
     private AudioTypeEnum type;
 
     @ApiModelProperty(value = "是否启用,0:停用,1:启用")
-    private Integer enable = 1;
+    private Boolean enable = true;
 
     @ApiModelProperty(value = "是否默认,SYS:系统内置,CUSTOM:自定义")
     private AudioDefaultEnum audioDefault = AudioDefaultEnum.SYS;
@@ -144,11 +144,11 @@ public class TEAudio extends BaseEntity implements Serializable {
         this.type = type;
     }
 
-    public Integer getEnable() {
+    public Boolean getEnable() {
         return enable;
     }
 
-    public void setEnable(Integer enable) {
+    public void setEnable(Boolean enable) {
         this.enable = enable;
     }
 

+ 62 - 1
themis-business/src/main/java/com/qmth/themis/business/entity/TMTencentVideoMessage.java

@@ -24,9 +24,25 @@ public class TMTencentVideoMessage implements Serializable {
     @ApiModelProperty(value = "主键")
     private Long id;
 
+    @ApiModelProperty(value = "考试记录id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long examRecordId;
+
     @ApiModelProperty(value = "请求id")
     private String requestId;
 
+    @ApiModelProperty(value = "视频源")
+    private String videoSource;
+
+    @ApiModelProperty(value = "开始时间")
+    private Long startTime;
+
+    @ApiModelProperty(value = "开始时间")
+    private Long endTime;
+
+    @ApiModelProperty(value = "视频地址")
+    private String videoUrl;
+
     @ApiModelProperty(value = "请求内容")
     private String object;
 
@@ -37,11 +53,56 @@ public class TMTencentVideoMessage implements Serializable {
 
     }
 
-    public TMTencentVideoMessage(String requestId, String object) {
+    public TMTencentVideoMessage(String requestId, String object, Long examRecordId, String videoSource, Long startTime, Long endTime, String videoUrl) {
         this.id = UidUtil.nextId();
         this.requestId = requestId;
         this.object = object;
         this.createTime = System.currentTimeMillis();
+        this.examRecordId = examRecordId;
+        this.videoSource = videoSource;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.videoUrl = videoUrl;
+    }
+
+    public Long getExamRecordId() {
+        return examRecordId;
+    }
+
+    public void setExamRecordId(Long examRecordId) {
+        this.examRecordId = examRecordId;
+    }
+
+    public String getVideoSource() {
+        return videoSource;
+    }
+
+    public void setVideoSource(String videoSource) {
+        this.videoSource = videoSource;
+    }
+
+    public Long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Long startTime) {
+        this.startTime = startTime;
+    }
+
+    public Long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Long endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getVideoUrl() {
+        return videoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        this.videoUrl = videoUrl;
     }
 
     public Long getId() {

+ 9 - 9
themis-business/src/main/java/com/qmth/themis/business/entity/TOeExamRecord.java

@@ -274,8 +274,8 @@ public class TOeExamRecord implements Serializable {
     @TableField(value = "mobile_second_monitor_status")
     private MonitorStatusSourceEnum mobileSecondMonitorStatus;
 
-    @ApiModelProperty(name = "转录视频地址")
-    private String tencentVideoUrl;
+//    @ApiModelProperty(name = "转录视频地址")
+//    private String tencentVideoUrl;
 
     @ApiModelProperty(value = "更新时间")
     private Long updateTime;
@@ -288,13 +288,13 @@ public class TOeExamRecord implements Serializable {
         this.updateTime = updateTime;
     }
 
-    public String getTencentVideoUrl() {
-        return tencentVideoUrl;
-    }
-
-    public void setTencentVideoUrl(String tencentVideoUrl) {
-        this.tencentVideoUrl = tencentVideoUrl;
-    }
+//    public String getTencentVideoUrl() {
+//        return tencentVideoUrl;
+//    }
+//
+//    public void setTencentVideoUrl(String tencentVideoUrl) {
+//        this.tencentVideoUrl = tencentVideoUrl;
+//    }
 
     public String getMonitorRecord() {
         return monitorRecord;

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

@@ -133,7 +133,7 @@ public enum ExamRecordFieldEnum {
 
     monitor_record("monitorRecord"),
 
-    tencent_video_url("tencentVideoUrl"),
+//    tencent_video_url("tencentVideoUrl"),
 
     update_time("updateTime");
 

+ 13 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TEStudentService.java

@@ -6,7 +6,9 @@ import com.qmth.themis.business.bean.StudentParams;
 import com.qmth.themis.business.bean.admin.StudentPhotoUploadResponseBean;
 import com.qmth.themis.business.dto.response.TEStudentDto;
 import com.qmth.themis.business.dto.response.TEStudentExamRecordDto;
+import com.qmth.themis.business.dto.response.TEStudentExamRecordVideoDto;
 import com.qmth.themis.business.entity.TEStudent;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.Map;
@@ -60,4 +62,15 @@ public interface TEStudentService extends IService<TEStudent> {
      * @param studentParams
      */
     public void updatePwd(StudentParams studentParams);
+
+    /**
+     * 视频监考回放查询接口
+     *
+     * @param iPage
+     * @param examRecordId
+     * @param monitorVideoSource
+     * @param log
+     * @return
+     */
+    public TEStudentExamRecordVideoDto examRecordVideoQuery(IPage<Map> iPage, Long examRecordId, MonitorVideoSourceEnum monitorVideoSource, Boolean log);
 }

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

@@ -495,4 +495,12 @@ public interface TOeExamRecordService extends IService<TOeExamRecord> {
      * @return
      */
     public ExamRecordPaperViewBean examRecordPaperView(Long examRecordId) throws IOException;
+
+    /**
+     * 查询没有视频地址的考试记录
+     *
+     * @param examId
+     * @return
+     */
+    public List<TOeExamRecord> findExamRecordByNotVideoUrl(Long examId);
 }

+ 55 - 13
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEStudentServiceImpl.java

@@ -4,18 +4,22 @@ import com.aliyun.oss.common.utils.BinaryUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.google.common.reflect.TypeToken;
-import com.google.gson.Gson;
 import com.qmth.themis.business.bean.StudentParams;
 import com.qmth.themis.business.bean.admin.StudentPhotoUploadResponseBean;
+import com.qmth.themis.business.cache.bean.ExamCacheBean;
+import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
+import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dao.TEStudentMapper;
 import com.qmth.themis.business.dto.response.TEStudentDto;
 import com.qmth.themis.business.dto.response.TEStudentExamRecordDto;
-import com.qmth.themis.business.dto.response.TEStudentMonitorRecordDto;
+import com.qmth.themis.business.dto.response.TEStudentExamRecordVideoDto;
+import com.qmth.themis.business.dto.response.TEStudentExamRecordVideoMessageDto;
 import com.qmth.themis.business.entity.TBUser;
+import com.qmth.themis.business.entity.TEExamStudentLog;
 import com.qmth.themis.business.entity.TEStudent;
-import com.qmth.themis.business.service.CacheService;
-import com.qmth.themis.business.service.TEStudentService;
+import com.qmth.themis.business.entity.TOeExamRecord;
+import com.qmth.themis.business.enums.MonitorVideoSourceEnum;
+import com.qmth.themis.business.service.*;
 import com.qmth.themis.business.util.OssUtil;
 import com.qmth.themis.business.util.ServletUtil;
 import com.qmth.themis.common.enums.ExceptionResultEnum;
@@ -53,6 +57,14 @@ public class TEStudentServiceImpl extends ServiceImpl<TEStudentMapper, TEStudent
     @Resource
     CacheService cacheService;
 
+    @Resource
+    TEExamService teExamService;
+
+    @Resource
+    TEExamStudentService teExamStudentService;
+
+    @Resource
+    TEExamStudentLogService teExamStudentLogService;
 
     /**
      * 学生档案查询
@@ -81,14 +93,7 @@ public class TEStudentServiceImpl extends ServiceImpl<TEStudentMapper, TEStudent
      */
     @Override
     public IPage<TEStudentExamRecordDto> studentExamRecordQuery(IPage<Map> iPage, Long studentId, Long examId) {
-        IPage<TEStudentExamRecordDto> teStudentExamRecordDtoIPage = teStudentMapper.studentExamRecordQuery(iPage, studentId, examId);
-        Gson gson = new Gson();
-        for (TEStudentExamRecordDto t : teStudentExamRecordDtoIPage.getRecords()) {
-            List<TEStudentMonitorRecordDto> monitorRecordList = gson.fromJson(t.getTencentVideoUrl(), new TypeToken<List<TEStudentMonitorRecordDto>>() {
-            }.getType());
-            t.setMonitorRecord(monitorRecordList);
-        }
-        return teStudentExamRecordDtoIPage;
+        return teStudentMapper.studentExamRecordQuery(iPage, studentId, examId);
     }
 
     @Transactional
@@ -143,4 +148,41 @@ public class TEStudentServiceImpl extends ServiceImpl<TEStudentMapper, TEStudent
         this.updateById(teStudent);
         cacheService.updateStudentAccountCache(teStudent.getId());
     }
+
+    /**
+     * 视频监考回放查询接口
+     *
+     * @param iPage
+     * @param examRecordId
+     * @param monitorVideoSource
+     * @param log
+     * @return
+     */
+    @Override
+    public TEStudentExamRecordVideoDto examRecordVideoQuery(IPage<Map> iPage, Long examRecordId, MonitorVideoSourceEnum monitorVideoSource, Boolean log) {
+        TOeExamRecord tOeExamRecord = null;
+        ExamCacheBean examCacheBean = null;
+        ExamStudentCacheBean examStudentCacheBean = null;
+
+        List<TEExamStudentLog> teExamStudentLogList = null;
+        if (Objects.nonNull(log) && log) {
+            tOeExamRecord = SystemConstant.getExamRecord(examRecordId);
+            examCacheBean = teExamService.getExamCacheBean(tOeExamRecord.getExamId());
+            examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(tOeExamRecord.getExamStudentId());
+
+            QueryWrapper<TEExamStudentLog> teExamStudentLogQueryWrapper = new QueryWrapper<>();
+            teExamStudentLogQueryWrapper.lambda().eq(TEExamStudentLog::getExamStudentId, examStudentCacheBean.getId())
+                    .eq(TEExamStudentLog::getExamRecordId, examRecordId)
+                    .orderByAsc(TEExamStudentLog::getCreateTime);
+            teExamStudentLogList = teExamStudentLogService.list(teExamStudentLogQueryWrapper);
+        }
+        IPage<TEStudentExamRecordVideoMessageDto> teStudentExamRecordVideoMessageDtoIPage = teStudentMapper.examRecordVideoQuery(iPage, examRecordId, Objects.nonNull(monitorVideoSource) ? monitorVideoSource.name().toLowerCase() : null);
+
+        TEStudentExamRecordVideoDto teStudentExamRecordVideoDto = new TEStudentExamRecordVideoDto(Objects.nonNull(examCacheBean) ? examCacheBean.getId() : null,
+                Objects.nonNull(examCacheBean) ? examCacheBean.getName() : null, Objects.nonNull(examStudentCacheBean) ? examStudentCacheBean.getId() : null, examRecordId, Objects.nonNull(examStudentCacheBean) ? examStudentCacheBean.getCourseCode() : null,
+                Objects.nonNull(examStudentCacheBean) ? examStudentCacheBean.getCourseName() : null, Objects.nonNull(examStudentCacheBean) ? examStudentCacheBean.getIdentity() : null, Objects.nonNull(examStudentCacheBean) ? examStudentCacheBean.getName() : null,
+                Objects.nonNull(tOeExamRecord) ? tOeExamRecord.getFirstStartTime() : null, teExamStudentLogList, teStudentExamRecordVideoMessageDtoIPage);
+        //TODO 兼容1.2.3之前的版本
+        return teStudentExamRecordVideoDto;
+    }
 }

+ 58 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/TOeExamRecordServiceImpl.java

@@ -42,6 +42,8 @@ import java.io.File;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.util.*;
 
 /**
@@ -1413,6 +1415,17 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
                 Objects.nonNull(tSyncExamStudentScore) && Objects.nonNull(tSyncExamStudentScore.getSubjectiveScoreDetail()) ? tSyncExamStudentScore.getSubjectiveScoreDetail() : null);
     }
 
+    /**
+     * 查询没有视频地址的考试记录
+     *
+     * @param examId
+     * @return
+     */
+    @Override
+    public List<TOeExamRecord> findExamRecordByNotVideoUrl(Long examId) {
+        return tOeExamRecordMapper.findExamRecordByNotVideoUrl(examId);
+    }
+
     /**
      * 取最高分
      *
@@ -1444,6 +1457,51 @@ public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, T
         return tOeExamRecord;
     }
 
+    public static void main(String[] args) {
+        Integer breakExpireSeconds = 600;
+        String level = null;
+        if (breakExpireSeconds.intValue() > 0) {
+            List<String> list = SystemConstant.mqDelayLevelList.subList(4, 16);
+            if (breakExpireSeconds.intValue() <= 60) {
+                level = "1m";
+            } else {
+                Integer time = breakExpireSeconds.intValue() / 60;
+                if (time.intValue() >= 30) {
+                    if (time.intValue() >= 30 && time.intValue() < 60) {
+                        level = "30m";
+                    } else if (time.intValue() >= 60 && time.intValue() < 120) {
+                        level = "1h";
+                    } else {
+                        level = "2h";
+                    }
+                } else {
+                    for (String s : list) {
+                        Integer value = Integer.parseInt(s.substring(0, s.length() - 1));
+                        if (time.intValue() <= value.intValue()) {
+                            level = value + "m";
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        System.out.println(level);
+        Map<String, Object> tranMap = new HashMap<>();
+        //发送延时mq消息start
+        Integer time = SystemConstant.mqDelayLevel.get(level);
+        LocalDateTime dt = LocalDateTime.now();
+        if (level.contains("m")) {
+            dt = dt.plusMinutes(Long.parseLong(level.replace("m", "")));
+        } else if (level.contains("s")) {
+            dt = dt.plusSeconds(Long.parseLong(level.replace("s", "")));
+        } else if (level.contains("h")) {
+            dt = dt.plusHours(Long.parseLong(level.replace("h", "")));
+        }
+        tranMap.put("timeOut", time);
+        tranMap.put("mqExecTime", dt.toInstant(ZoneOffset.of("+8")).toEpochMilli());
+        System.out.println(tranMap);
+    }
+
     /**
      * 发送断点延时mq消息
      *

+ 29 - 2
themis-business/src/main/resources/mapper/TEStudentMapper.xml

@@ -58,9 +58,16 @@
             tees.name,
             toer.objective_score as objectiveScore,
             toer.status,
-            toer.tencent_video_url as tencentVideoUrl,
             toer.first_start_time as firstStartTime,
-            toer.finish_time as finishTime
+            toer.finish_time as finishTime,
+            IFNULL(IF(toer.tencent_video_url is not null, 1, null),(
+            select
+            count(1)
+            from
+            t_m_tencent_video_message t
+            where
+            t.exam_record_id = toer.id)) as videoCount,
+            tee.monitor_video_source as monitorVideoSource
         from
             t_e_exam_student tees
         left join t_e_exam tee on
@@ -79,4 +86,24 @@
             </where>
         order by toer.first_start_time desc
     </select>
+
+    <select id="examRecordVideoQuery" resultType="com.qmth.themis.business.dto.response.TEStudentExamRecordVideoMessageDto">
+        select
+            tmtvm.video_source as videoSource,
+            tmtvm.start_time as startTime,
+            tmtvm.end_time as endTime,
+            tmtvm.video_url as videoUrl
+        from
+            t_m_tencent_video_message tmtvm
+                join t_oe_exam_record toer on
+                toer.id = tmtvm.exam_record_id
+        <where>
+            <if test="examRecordId != null and examRecordId != ''">
+                and toer.id = #{examRecordId}
+            </if>
+            <if test="monitorVideoSource != null and monitorVideoSource != ''">
+                and tmtvm.video_source like concat('%', #{monitorVideoSource})
+            </if>
+        </where>
+    </select>
 </mapper>

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

@@ -1688,4 +1688,21 @@
         and teesol.create_time <![CDATA[ <= ]]> #{currentTime})
         and teesol.`type` = 'OFF_LINE') t where t.createTime <![CDATA[ <= ]]> #{item}
     </sql>
+
+    <select id="findExamRecordByNotVideoUrl" resultType="com.qmth.themis.business.entity.TOeExamRecord">
+        select
+            toer.*
+        from
+            t_oe_exam_record toer
+        where
+            not EXISTS(
+                    select
+                        tmtvm.exam_record_id
+                    from
+                        t_m_tencent_video_message tmtvm
+                    where
+                        tmtvm.exam_record_id = toer.id
+                )
+          and toer.exam_id = #{examId}
+    </select>
 </mapper>

+ 1 - 1
themis-exam/src/main/resources/application-dev.properties

@@ -168,7 +168,7 @@ tencentyun.sdk.secretId=AKIDKUO2PVLuCDxQcW9VaOuA8pFOGq9BwQdZ
 tencentyun.sdk.secretKey=g5D4dwxhByXrvjGWVDfqkPqzrSmqd9OM
 tencentyun.sdk.vodAppId=1500002365
 tencentyun.sdk.callbackPwd=123456
-tencentyun.sdk.callbackTime=10m
+tencentyun.sdk.callbackTime=2m
 tencentyun.sdk.trtcQueryUrl=trtc.tencentcloudapi.com
 tencentyun.sdk.trtcRegion=ap-guangzhou
 

+ 13 - 29
themis-mq/src/main/java/com/qmth/themis/mq/service/impl/MqLogicServiceImpl.java

@@ -1,5 +1,6 @@
 package com.qmth.themis.mq.service.impl;
 
+import cn.hutool.core.date.DateUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -43,7 +44,10 @@ import javax.annotation.Resource;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.security.NoSuchAlgorithmException;
-import java.util.*;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -327,36 +331,23 @@ public class MqLogicServiceImpl implements MqLogicService {
         Map<String, Object> resultMap = tencentYunUtil.callTencentVideo(recordId);
 
         if (!CollectionUtils.isEmpty(resultMap)) {
-            TOeExamRecord tOeExamRecord = SystemConstant.getExamRecord(recordId);
             SearchMediaResponse response = (SearchMediaResponse) resultMap.get("object");
             if (Objects.nonNull(response) && response.getTotalCount().intValue() > 0) {
-                Map<String, String> map = new HashMap<>();
-                JSONArray jsonArray = null;
-                int countDb = 0;
-                if (Objects.nonNull(tOeExamRecord.getTencentVideoUrl())) {
-                    jsonArray = JSONArray.parseArray(tOeExamRecord.getTencentVideoUrl());
-                    countDb = jsonArray.size();
-                    for (int i = 0; i < countDb; i++) {
-                        JSONObject jsonObject = (JSONObject) jsonArray.get(i);
-                        String videoSource = SystemConstant.getMonitorRecordStreamId((String) jsonObject.get(SystemConstant.VIDEO_SOURCE));
-                        map.put(videoSource, videoSource);
-                    }
-                } else {
-                    jsonArray = new JSONArray();
-                }
                 MediaInfo[] mediaInfos = response.getMediaInfoSet();
                 for (int i = 0; i < mediaInfos.length; i++) {
                     MediaBasicInfo mediaBasicInfo = mediaInfos[i].getBasicInfo();
-                    String videoSource = SystemConstant.getMonitorRecordStreamId(mediaBasicInfo.getName());
+                    String mediaBasicName = mediaBasicInfo.getName();
+                    String videoSource = SystemConstant.getMonitorRecordStreamId(mediaBasicName);
+                    String[] timeArrays = mediaBasicName.split("_");
+                    Long startTime = DateUtil.parse(timeArrays[6], SystemConstant.VIDEO_TIME_FORMAT).getTime();
+                    Long endTime = DateUtil.parse(timeArrays[7], SystemConstant.VIDEO_TIME_FORMAT).getTime();
                     boolean lock = false;
                     for (int y = 0; y < SystemConstant.CONSUME_MESSAGE_BATCH_MAX_SIZE; y++) {
                         lock = redisUtil.lock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource, SystemConstant.REDIS_LOCK_TENCENT_VIDEO_TIME_OUT);
-                        if (lock && Objects.isNull(map.get(videoSource))) {
+                        if (lock) {
                             try {
-                                JSONObject jsonObject = new JSONObject();
-                                jsonObject.put(SystemConstant.VIDEO_SOURCE, videoSource);
-                                jsonObject.put(SystemConstant.VIDEO_URL, mediaBasicInfo.getMediaUrl());
-                                jsonArray.add(jsonObject);
+                                TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(response.getRequestId(), JacksonUtil.parseJson(response), recordId, videoSource, startTime, endTime, mediaBasicInfo.getMediaUrl());
+                                tencentVideoMessageService.save(tencentVideoMessage);
                                 break;
                             } finally {
                                 redisUtil.releaseLock(SystemConstant.REDIS_LOCK_TENCENT_VIDEO_PREFIX + videoSource);
@@ -370,13 +361,6 @@ public class MqLogicServiceImpl implements MqLogicService {
                         }
                     }
                 }
-                if (Objects.nonNull(jsonArray) && jsonArray.size() > 0 && countDb != jsonArray.size()) {
-                    tOeExamRecord.setTencentVideoUrl(jsonArray.toJSONString());
-                    tOeExamRecordService.updateById(tOeExamRecord);
-
-                    TMTencentVideoMessage tencentVideoMessage = new TMTencentVideoMessage(response.getRequestId(), JacksonUtil.parseJson(response));
-                    tencentVideoMessageService.save(tencentVideoMessage);
-                }
                 tmRocketMessageService.saveMqMessageSuccess(mqDto, key);
             }
         } else {

+ 6 - 2
themis-task/src/main/resources/application-dev.properties

@@ -211,8 +211,12 @@ tencentyun.sdk.secretId=AKIDKUO2PVLuCDxQcW9VaOuA8pFOGq9BwQdZ
 tencentyun.sdk.secretKey=g5D4dwxhByXrvjGWVDfqkPqzrSmqd9OM
 tencentyun.sdk.vodAppId=1500002365
 tencentyun.sdk.callbackPwd=123456
-tencentyun.sdk.callbackTime=10m
+tencentyun.sdk.callbackTime=2m
 tencentyun.sdk.trtcQueryUrl=trtc.tencentcloudapi.com
 tencentyun.sdk.trtcRegion=ap-guangzhou
 
-monitor.config.prefix=oe_test
+monitor.config.prefix=oe_test
+
+#\u4E91\u9605\u5377\u540C\u6B65\u914D\u7F6E
+yun.mark.url=https://epcc.markingcloud.com/admin/home
+yun.mark.studentScoreApi=/api/exam/student/score

+ 5 - 1
themis-task/src/main/resources/application-main-temp.properties

@@ -167,4 +167,8 @@ mq.config.map.EXAM_BREAK_GROUP=themis-group-exam-examBreak
 mq.config.map.EXAM_BREAK_DELAY_GROUP=themis-group-exam-examBreakDelay
 mq.config.map.WEBSOCKET_OE_GROUP=themis-group-exam-websocketOe
 mq.config.map.WEBSOCKET_OE_MOBILE_GROUP=themis-group-exam-websocketOeMobile
-mq.config.map.TENCENT_VIDEO_GROUP=themis-group-exam-tencentVideo
+mq.config.map.TENCENT_VIDEO_GROUP=themis-group-exam-tencentVideo
+
+#\u4E91\u9605\u5377\u540C\u6B65\u914D\u7F6E
+yun.mark.url=https://epcc.markingcloud.com/admin/home
+yun.mark.studentScoreApi=/api/exam/student/score