package com.qmth.themis.business.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;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.reflect.TypeToken;
import com.qmth.themis.business.bean.admin.*;
import com.qmth.themis.business.bean.status.*;
import com.qmth.themis.business.cache.ExamRecordCacheUtil;
import com.qmth.themis.business.cache.ExamingDataCacheUtil;
import com.qmth.themis.business.cache.RedisKeyHelper;
import com.qmth.themis.business.cache.bean.*;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dao.TOeExamRecordMapper;
import com.qmth.themis.business.dto.MqDto;
import com.qmth.themis.business.dto.response.MarkResultDto;
import com.qmth.themis.business.dto.response.TEStudentMonitorRecordDto;
import com.qmth.themis.business.entity.*;
import com.qmth.themis.business.enums.*;
import com.qmth.themis.business.service.*;
import com.qmth.themis.business.util.*;
import com.qmth.themis.common.contanst.Constants;
import com.qmth.themis.common.enums.ExceptionResultEnum;
import com.qmth.themis.common.exception.BusinessException;
import com.qmth.themis.common.util.FileUtil;
import com.qmth.themis.common.util.GsonUtil;
import com.qmth.themis.common.util.SimpleBeanUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 考试记录 服务实现类
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Service
public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, TOeExamRecord>
        implements TOeExamRecordService {

    private final static Logger log = LoggerFactory.getLogger(TOeExamRecordServiceImpl.class);

    @Resource
    MqDtoService mqDtoService;

    @Resource
    TOeExamRecordMapper tOeExamRecordMapper;

    @Resource
    RedisUtil redisUtil;

    @Resource
    TEExamPaperService examPaperService;

    @Resource
    TEExamService examService;

    @Resource
    TOeExamAnswerService examAnswerService;

    @Resource
    TEExamActivityService examActivityService;

    @Resource
    TEExamStudentService teExamStudentService;

    @Resource
    OssUtil ossUtil;

    @Resource
    UidUtil uidUtil;

    @Resource
    MqUtil mqUtil;

    @Resource
    TGErrorService tgErrorService;

    @Resource
    TEExamPaperService teExamPaperService;

    @Resource
    TOeExamAnswerService tOeExamAnswerService;

    @Resource
    TSyncExamStudentScoreService tSyncExamStudentScoreService;

//    @Resource
//    TOeExamRecordService tOeExamRecordService;

    @Resource
    TEOrgSummaryService teOrgSummaryService;

    @Resource
    ThemisCacheService themisCacheService;

    @Resource
    TBOrgService tbOrgService;

    @Resource
    TMTencentVideoMessageService tmTencentVideoMessageService;

    @Resource
    TEExamSummaryService teExamSummaryService;

    @Transactional
    @Override
    public TOeExamRecord saveByPrepare(Long examId, Long examActivityId, Long examStudentId, Long paperId,
                                       Integer serialNumber, Long studentId, Integer mobilePhotoUpload) {
        ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(examActivityId);
        ExamCacheBean exam = examService.getExamCacheBean(examId);
        TOeExamRecord er = new TOeExamRecord();
        er.setId(uidUtil.getId());
        er.setExamId(examId);
        er.setExamActivityId(examActivityId);
        er.setExamStudentId(examStudentId);
        er.setPaperId(paperId);
        er.setSerialNumber(serialNumber);
        er.setFirstPrepareTime(System.currentTimeMillis());
        er.setStatus(ExamRecordStatusEnum.FIRST_PREPARE);
        er.setObjectiveScore(0.0);
//        er.setMonitorKey(String.valueOf(redisUtil.getRedisSequence()));
        er.setMonitorKey(String.valueOf(er.getId()));
//        er.setAlreadyBreakCount(0);
        er.setStartTime(ac.getStartTime());
        er.setEndTime(ac.getFinishTime());
        er.setOpeningSeconds(ac.getOpeningSeconds());
        er.setMinDurationSeconds(exam.getMinDurationSeconds());
        er.setMaxDurationSeconds(ac.getMaxDurationSeconds());
        er.setForceFinish(exam.getForceFinish());
        er.setCameraPhotoUpload(exam.getCameraPhotoUpload());
        er.setMobilePhotoUpload(mobilePhotoUpload);
        er.setEntryAuthenticationPolicy(exam.getEntryAuthenticationPolicy());
        er.setInProcessFaceVerifyStatus(exam.getInProcessFaceVerify());
        er.setInProcessLivenessVerifyStatus(exam.getInProcessLivenessVerify());
        er.setInProcessRealnessVerifyStatus(exam.getInProcessRealnessVerify());
        er.setInProcessFaceStrangerIgnore(exam.getInProcessFaceStrangerIgnore());
        er.setInProcessLivenessFixedRange(exam.getInProcessLivenessFixedRange());
        er.setInProcessLivenessJudgePolicy(exam.getInProcessLivenessJudgePolicy());
        er.setMonitorVideoSource(exam.getMonitorVideoSource());
        er.setMonitorRecord(exam.getMonitorRecord());
        ExamPaperCacheBean ep = examPaperService.getExamPaperCacheBean(paperId);
        if (StringUtils.isBlank(ep.getAnswerPath())) {
            er.setHasAnswerFile(0);
        } else {
            er.setHasAnswerFile(1);
        }
        Map<String, Object> map = SimpleBeanUtil.objectToMap(er);
        ExamStatusBean examStatusBean = new ExamStatusBean(ExamRecordStatusEnum.FIRST_PREPARE, System.currentTimeMillis());
        map.put(SystemConstant.STATUS, examStatusBean);
        redisUtil.setForHash(RedisKeyHelper.examRecordCacheKey(er.getId()), map);
        sendExamRecordDataSaveMq(er.getId(), System.currentTimeMillis());

        //2022-04-15新增
        ExamingDataCacheUtil.setExamingRecordId(studentId, er.getId());
        return er;
    }

    /**
     * 计算客观分
     */
//    @Override
//    public void calculateObjectiveScore(Map<String, Object> param) {
//        Long recordId = (Long) param.get("recordId");
//        Integer mainNumber = (Integer) param.get("mainNumber");
//        Integer subNumber = (Integer) param.get("subNumber");
//        Integer subIndex = (Integer) param.get("subIndex");
//        Long paperId = ExamRecordCacheUtil.getPaperId(recordId);
//        String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
//
//        //考生作答缓存
//        ExamStudentAnswerCacheBean answer = (ExamStudentAnswerCacheBean) redisUtil
//                .get(RedisKeyHelper.examAnswerKey(recordId), key);
//        if (answer == null) {
//            log.error("no ExamStudentAnswerCacheBean for calculateObjectiveScore recordId:" + recordId + " key:" + key);
//
//            // 计算客观分总分
//            calculateTotalObjectiveScore(param);
//            return;
//        }
//        //整卷客观题标答缓存集合
//        Map<String, ObjectiveAnswerCacheBean> map = examPaperService.getObjectiveAnswerCacheBean(paperId);
//        if (map == null || map.size() == 0) {
//            log.info("no ObjectiveAnswerCacheBean map for calculateObjectiveScore recordId:" + recordId + " paperId:"
//                    + paperId);
//            // 更新分数
//            answer.setScore(0.0);
//            redisUtil.set(RedisKeyHelper.examAnswerKey(recordId), key, answer);
//
//            // 计算客观分总分
//            calculateTotalObjectiveScore(param);
//            return;
//        }
//        //客观题标答缓存
//        ObjectiveAnswerCacheBean cb = map.get(key);
//        if (cb == null) {
//            log.info("no ObjectiveAnswerCacheBean for calculateObjectiveScore recordId:" + recordId + " key:" + key);
//            // 更新分数
//            answer.setScore(0.0);
//            redisUtil.set(RedisKeyHelper.examAnswerKey(recordId), key, answer);
//
//            // 计算客观分总分
//            calculateTotalObjectiveScore(param);
//            return;
//        }
//
//        if (cb.getStructType().intValue() == 1) {
//            if (checkSingleChoice(answer.getAnswer(), cb.getChoiceAnswer())) {
//                answer.setScore(cb.getScore());
//            } else {
//                answer.setScore(0.0);
//            }
//        }
//        if (cb.getStructType().intValue() == 2) {
//            ExamCacheBean ec = examService.getExamCacheBean(ExamRecordCacheUtil.getExamId(recordId));
//            int r = checkMultipleChoice(answer.getAnswer(), cb.getChoiceAnswer());
//            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.EQUAL)) {// 全对给分
//                if (r == 1) {
//                    answer.setScore(cb.getScore());
//                } else {
//                    answer.setScore(0.0);
//                }
//            }
//            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.PARTIAL)) {// 漏选半分
//                if (r == 1) {
//                    answer.setScore(cb.getScore());
//                } else if (r == 0) {
//                    BigDecimal b = new BigDecimal(cb.getScore()).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_UP);
//                    answer.setScore(b.doubleValue());
//                } else {
//                    answer.setScore(0.0);
//                }
//            }
//        }
//        if (cb.getStructType().intValue() == 3) {
//            if (StringUtils.isNotBlank(answer.getAnswer())) {
//                String answerStr = answer.getAnswer().replaceAll("\n", "");
//                if (StringUtils.isNotBlank(answerStr)) {
//                    if (Boolean.parseBoolean(answerStr) == cb.getBoolAnswer().booleanValue()) {
//                        answer.setScore(cb.getScore());
//                    } else {
//                        answer.setScore(0.0);
//                    }
//                }
//            }
//        }
//
//        if (answer.getScore() == null) {
//            log.info("小题分数为空，得分置为0，" + "recordId:" + recordId + " questionKey:" + key);
//            answer.setScore(0.0);
//        }
//        // 更新分数
//        redisUtil.set(RedisKeyHelper.examAnswerKey(recordId), key, answer);
//
//        // 计算客观分总分
//        calculateTotalObjectiveScore(param);
//    }

//    @SuppressWarnings("unchecked")
//    private void calculateTotalObjectiveScore(Map<String, Object> param) {
//        Long recordId = (Long) param.get("recordId");
//        Integer mainNumber = (Integer) param.get("mainNumber");
//        Integer subNumber = (Integer) param.get("subNumber");
//        Integer subIndex = (Integer) param.get("subIndex");
//        String lockKey = SystemConstant.REDIS_LOCK_TOTAL_OBJECTIVE_SCORE_PREFIX + recordId;
//        try {
//            String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
//            Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
//            if (lock) {
//                log.info("calculateTotalObjectiveScore get lock sucss recordId:" + recordId + " key:" + key);
//                Map<String, ExamStudentAnswerCacheBean> as = redisUtil
//                        .getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
//                if (as != null && as.size() > 0) {
//                    Double total = 0.0;
//                    for (ExamStudentAnswerCacheBean sa : as.values()) {
//                        if (sa.getObjective() != null && sa.getObjective()) {//是客观题
//                            if (sa.getScore() != null) {//有分值
//                                total = total + sa.getScore();
//                            } else {
//                                log.info(
//                                        "calculateTotalObjectiveScore ExamStudentAnswerCacheBean Score is null recordId:"
//                                                + recordId + " key:" + RedisKeyHelper
//                                                .examAnswerHashKey(sa.getMainNumber(), sa.getSubNumber(),
//                                                        sa.getSubIndex()));
//                                total = null;
//                                break;
//                            }
//                        }
//                    }
//                    if (total != null) {
//                        ExamRecordCacheUtil.setObjectiveScore(recordId, total);
//                        this.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
//                    } else {
//                        log.info("calculateTotalObjectiveScore total Score is null recordId:" + recordId + " key:"
//                                + key);
//                    }
//                }
//            } else {
//                log.info("calculateTotalObjectiveScore get lock faild recordId:" + recordId + " key:" + key);
//                calculateObjectiveScoreMsg(recordId, mainNumber, subNumber, subIndex);
//            }
//        } finally {
//            redisUtil.releaseLock(lockKey);
//        }
//    }

    //考试中计算客观分消息
//    private void calculateObjectiveScoreMsg(Long recordId, Integer mainNumber, Integer subNumber, Integer subIndex) {
//        Map<String, Object> transMap = new HashMap<String, Object>();
//        transMap.put("recordId", recordId);
//        transMap.put("mainNumber", mainNumber);
//        transMap.put("subNumber", subNumber);
//        transMap.put("subIndex", subIndex);
//        Map<String, Object> propMap = mqDtoService.buildMqDelayMsg("1s");
//        MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.CALCULATE_OBJECTIVE_SCORE.name(),
//                transMap, MqTagEnum.CALCULATE_OBJECTIVE_SCORE, recordId.toString(), propMap, recordId.toString());
//        mqDtoService.assembleSendAsyncDelayMsg(mqDto);
//    }
    private boolean checkSingleChoice(String answerArray, JSONArray ar) {
        JSONArray an = JSONArray.parseArray(answerArray);
        if (an.size() == 0) {
            return false;
        }
        int a = an.getIntValue(0);
        return checkSingleChoice(a, ar);
    }

    private boolean checkSingleChoice(int answer, JSONArray ar) {
        for (int i = 0; i < ar.size(); i++) {
            if (answer == ar.getIntValue(i)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param answer
     * @param ar
     * @return -1 有错选项，0-无措选项 未答全 1-全对
     */
    private int checkMultipleChoice(String answer, JSONArray ar) {
        int yes = 0;
        int no = 0;
        JSONArray an = JSONArray.parseArray(answer);
        for (int i = 0; i < an.size(); i++) {
            if (checkSingleChoice(an.getIntValue(i), ar)) {
                yes++;
                continue;
            } else {
                no++;
            }
        }
        if (yes == ar.size() && yes == an.size()) {
            return 1;
        }
        if (yes != 0 && no == 0) {
            return 0;
        }
        return -1;
    }

    /**
     * 考试记录数据持久化
     */
    @SuppressWarnings("unchecked")
    @Transactional
    @Override
    public void saveDataByCache(Long recordId) {
        String tempDir = SystemConstant.TEMP_FILES_DIR;
        String dir = tempDir + "/" + SystemConstant.getNanoId() + "/";
        File dfile = new File(dir);
        try {
            dfile.mkdirs();
            String structFilePath = DateUtil.format(new Date(), Constants.DATE_PATTERN) + "/" + SystemConstant.getNanoId() + SystemConstant.JSON_PREFIX;
            ExamStudentPaperStructCacheBean struct = (ExamStudentPaperStructCacheBean) redisUtil
                    .get(RedisKeyHelper.studentPaperStructKey(recordId));

            Long timestamp = System.currentTimeMillis();
            ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.PERSISTED, timestamp);
            ExamRecordCacheUtil.setUpdateTime(recordId, timestamp);

            //保存考试记录
            TOeExamRecord er = this.getCacheExamRecord(recordId);
            if (Objects.isNull(er.getId())) {
                er = SystemConstant.getExamRecord(recordId);
                er.setStatus(ExamRecordStatusEnum.PERSISTED);
                er.setUpdateTime(timestamp);
            }
            if (Objects.nonNull(struct)) {
                er.setPaperStructPath(structFilePath);
            }
            er.setPaperStructUpload(1);
            if (Objects.nonNull(er.getClientWebsocketStatus()) && er.getClientWebsocketStatus() == WebsocketStatusEnum.ON_LINE) {
                er.setClientWebsocketStatus(WebsocketStatusEnum.OFF_LINE);
            }
            saveOrUpdate(er);

            //保存作答
            Map<String, ExamStudentAnswerCacheBean> answerMap = redisUtil
                    .getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
            if (!CollectionUtils.isEmpty(answerMap)) {
                List<TOeExamAnswer> tOeExamAnswerList = new ArrayList<>(answerMap.size());
                for (ExamStudentAnswerCacheBean answerCache : answerMap.values()) {
                    TOeExamAnswer answer = new TOeExamAnswer();
                    BeanUtils.copyProperties(answerCache, answer);
                    answer.setId(uidUtil.getId());
                    answer.setExamRecordId(recordId);
                    tOeExamAnswerList.add(answer);
                }
                examAnswerService.saveOrUpdateBatch(tOeExamAnswerList);
            }
            //更新考生信息
            teExamStudentService.updateExamStudentByCache(er.getExamStudentId());
            ExamStudentCacheBean examStudentCache = teExamStudentService.getExamStudentCacheBean(er.getExamStudentId());
            ExamCacheBean examCacheBean = examService.getExamCacheBean(ExamRecordCacheUtil.getExamId(recordId));
            Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
            if (Objects.nonNull(examStudentCache.getAlreadyExamCount()) &&
                    examStudentCache.getAlreadyExamCount().longValue() == 1) {
                teOrgSummaryService.updateOrgZeroSummary(examCacheBean.getOrgId(), 1);
                teOrgSummaryService.updateOrgZeroSummary(0L, 1);
                teExamSummaryService.updateExamSummaryFinishCount(examCacheBean.getId(), examActivityId, examStudentCache.getRoomCode());
                themisCacheService.updateExamSummaryCache(examCacheBean.getId(), examActivityId, examStudentCache.getRoomCode());
                themisCacheService.updateExamSummaryCache(examCacheBean.getId(), examActivityId);
                themisCacheService.updateExamSummaryCache(examCacheBean.getId());
            } else {
                teOrgSummaryService.updateOrgZeroSummary(examCacheBean.getOrgId(), 0);
                teOrgSummaryService.updateOrgZeroSummary(0L, 0);
            }
            themisCacheService.updateOrgSummaryCache(0L);
            themisCacheService.updateOrgSummaryCache(examCacheBean.getOrgId());

            String examRecordChangeKey = examActivityId + "_" + examStudentCache.getRoomCode();
            themisCacheService.addExamRecordChangeCache(examRecordChangeKey, timestamp);

            //上传个人试卷结构
            if (Objects.nonNull(struct)) {
                ossUtil.upload(false, structFilePath, struct.getContent());
            }
        } catch (Exception e) {
            log.error("考试持久化失败", e);
            if (e instanceof BusinessException) {
                throw new BusinessException(e.getMessage());
            } else {
                throw new RuntimeException(e);
            }
        } finally {
            FileUtil.deleteFolder(dir);
        }
    }

    /**
     * 从缓存里获取考试记录
     *
     * @param recordId
     * @return
     */
    @Override
    public TOeExamRecord getCacheExamRecord(Long recordId) {
        TOeExamRecord er = null;
        Map<String, Object> record = redisUtil.getHashEntries(RedisKeyHelper.examRecordCacheKey(recordId));
        if (!CollectionUtils.isEmpty(record)) {
            Object o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.status.getCode());
            if (Objects.nonNull(o) && o instanceof ExamStatusBean) {
                record.put(ExamRecordFieldEnum.status.getCode(), ((ExamStatusBean) o).getExamRecordStatusEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.client_websocket_status.getCode());
            if (Objects.nonNull(o) && o instanceof ClientWebsocketStatusBean) {
                record.put(ExamRecordFieldEnum.client_websocket_status.getCode(), ((ClientWebsocketStatusBean) o).getWebsocketStatusEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.mobile_first_websocket_status.getCode());
            if (Objects.nonNull(o) && o instanceof MobileWebsocketStatusBean) {
                record.put(ExamRecordFieldEnum.mobile_first_websocket_status.getCode(), ((MobileWebsocketStatusBean) o).getWebsocketStatusEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.mobile_second_websocket_status.getCode());
            if (Objects.nonNull(o) && o instanceof MobileWebsocketStatusBean) {
                record.put(ExamRecordFieldEnum.mobile_second_websocket_status.getCode(), ((MobileWebsocketStatusBean) o).getWebsocketStatusEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.camera_monitor_status.getCode());
            if (Objects.nonNull(o) && o instanceof MonitorStatusBean) {
                record.put(ExamRecordFieldEnum.camera_monitor_status.getCode(), ((MonitorStatusBean) o).getMonitorStatusSourceEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.screen_monitor_status.getCode());
            if (Objects.nonNull(o) && o instanceof MonitorStatusBean) {
                record.put(ExamRecordFieldEnum.screen_monitor_status.getCode(), ((MonitorStatusBean) o).getMonitorStatusSourceEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.mobile_first_monitor_status.getCode());
            if (Objects.nonNull(o) && o instanceof MonitorStatusBean) {
                record.put(ExamRecordFieldEnum.mobile_first_monitor_status.getCode(), ((MonitorStatusBean) o).getMonitorStatusSourceEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.mobile_second_monitor_status.getCode());
            if (Objects.nonNull(o) && o instanceof MonitorStatusBean) {
                record.put(ExamRecordFieldEnum.mobile_second_monitor_status.getCode(), ((MonitorStatusBean) o).getMonitorStatusSourceEnum());
            }
            o = redisUtil.get(RedisKeyHelper.examRecordCacheKey(recordId), ExamRecordFieldEnum.breach_status.getCode());
            if (Objects.nonNull(o) && o instanceof BreachStatusBean) {
                record.put(ExamRecordFieldEnum.breach_status.getCode(), ((BreachStatusBean) o).getStatus());
            }
            er = GsonUtil.fromJson(GsonUtil.toJson(record), TOeExamRecord.class);
        }
        return er;
    }

    /**
     * 从数据库查询考试记录
     *
     * @param examId
     * @param activityId
     * @param examStudentId
     * @param examRecordStatusEnum
     * @return
     */
    @Override
    public TOeExamRecord getExamRecordForDb(Long examId, Long activityId, Long examStudentId, ExamRecordStatusEnum examRecordStatusEnum) {
        QueryWrapper<TOeExamRecord> tOeExamRecordQueryWrapper = new QueryWrapper<>();
        tOeExamRecordQueryWrapper.lambda().eq(TOeExamRecord::getExamId, examId)
                .eq(TOeExamRecord::getExamActivityId, activityId)
                .eq(TOeExamRecord::getExamStudentId, examStudentId)
                .eq(TOeExamRecord::getStatus, examRecordStatusEnum);
        return this.getOne(tOeExamRecordQueryWrapper);
    }

    /**
     * 根据id更新预警数量
     *
     * @param id
     */
    @Override
    public void updateWarningCountByDb(Long id) {
        this.baseMapper.updateWarningCount(id);
    }

    /**
     * 根据id更新预警未读数量
     *
     * @param id
     * @param number
     */
    @Override
    public void updateWarningUnreadByDb(Long id, Integer number) {
        this.baseMapper.updateWarningUnread(id, number);
    }

    /**
     * 更新预警总数
     *
     * @param id
     * @param tIeInvigilateWarnInfo
     */
    @Override
    public void updateWarningCountCache(Long id, TIeInvigilateWarnInfo tIeInvigilateWarnInfo) {
        ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(id);
        if (Objects.nonNull(status) && status != ExamRecordStatusEnum.PERSISTED) {
            ExamRecordCacheUtil.setWarningCount(id, 1);
            ExamRecordCacheUtil.setWarningUnread(id, 1);
            if (Objects.nonNull(tIeInvigilateWarnInfo) && tIeInvigilateWarnInfo.getType() == VerifyExceptionEnum.FACE_COUNT_ERROR
                    && Objects.equals(tIeInvigilateWarnInfo.getLevel(), "D8")) {
                this.updateWarningMultipleFaceCountCache(id);
            }
            this.sendExamRecordDataSaveMq(id, System.currentTimeMillis());
        } else {
            //已经结束了直接更新数据库
            this.sendExamRecordDataUpdateWarningCountMq(id);
        }
    }

    /**
     * 更新预警未读数
     *
     * @param id
     * @param number
     * @param updateDb
     */
    @Override
    public void updateWarningUnreadCache(Long id, Integer number, Boolean updateDb) {
        Integer warningUnread = ExamRecordCacheUtil.getWarningUnread(id);
        ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(id);
        if (Objects.nonNull(status) && status != ExamRecordStatusEnum.PERSISTED) {
            if (Objects.nonNull(warningUnread) && number.intValue() == 0) {
                ExamRecordCacheUtil.setWarningUnread(id, -warningUnread);
            } else if (Objects.nonNull(warningUnread) && number.intValue() > 0) {
                ExamRecordCacheUtil.setWarningUnread(id, -(warningUnread - number));
            } else {
                ExamRecordCacheUtil.setWarningUnread(id, 0);
            }
            if (Objects.nonNull(updateDb) && updateDb) {
                this.sendExamRecordDataSaveMq(id, System.currentTimeMillis());
            }
        } else {
            if (Objects.nonNull(updateDb) && updateDb) {
                //已经结束了直接更新数据库
                this.sendExamRecordDataUpdateWarningUnreadMq(id, number);
            }
        }
    }

    /**
     * 更新陌生人脸预警总数
     *
     * @param id
     */
    @Override
    public void updateWarningMultipleFaceCountCache(Long id) {
        ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(id);
        if (Objects.nonNull(status) && status != ExamRecordStatusEnum.PERSISTED) {
            ExamRecordCacheUtil.setWarningMultipleFaceCount(id, 1);
        }
    }

    /**
     * 更新异常数
     *
     * @param id
     */
    @Override
    public void updateExceptionCountCache(Long id) {
        ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(id);
        if (Objects.nonNull(status) && status != ExamRecordStatusEnum.PERSISTED) {
            ExamRecordCacheUtil.setExceptionCount(id, 1);
        }
    }

    /**
     * 根据预警条件清除未阅
     *
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param userId
     * @param orgId
     * @return
     */
    @Override
    public List<TOeExamRecord> findExamRecordByWarning(Long examId, Long examActivityId, String roomCode, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, Long userId, Long orgId) {
        return this.baseMapper.findExamRecordByWarning(examId, examActivityId, roomCode, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, userId, orgId);
    }

    /**
     * 查找完成人数
     *
     * @param examId
     * @param examActivityId
     * @return
     */
    @Override
    public Integer findExamRecordByFinish(Long examId, Long examActivityId, String roomCode) {
        return this.baseMapper.findExamRecordByFinish(examId, examActivityId, roomCode);
    }

    @Override
    public void saveFaceVerify(ExamTypeEnum type, Long recordId, Long entryAuthenticationId,
                               VerifyExceptionEnum entryAuthenticationResult) {
        if (ExamTypeEnum.FIRST_START.equals(type)) {
            ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId);
            ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult);
        }
        this.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
    }

    @Override
    public void saveLivenessVerify(LivenessTypeEnum type, Long recordId, Long entryAuthenticationId,
                                   VerifyExceptionEnum entryAuthenticationResult) {
        if (LivenessTypeEnum.FIRST_START.equals(type)) {
            ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId);
            ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult);
        } else if (LivenessTypeEnum.IN_PROCESS.equals(type)) {
            ExamRecordCacheUtil.setInProcessLivenessVerifyCount(recordId, 1);
        }
        this.sendExamRecordDataSaveMq(recordId, System.currentTimeMillis());
    }

    /**
     * 保存考试记录数据
     *
     * @param recordId
     */
    @Override
    @Transactional
    public void examRecordDataSave(Long recordId) {
        ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
        if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum != ExamRecordStatusEnum.PERSISTED) {
            TOeExamRecord er = this.getCacheExamRecord(recordId);
            this.saveOrUpdate(er);
        }
    }

    /**
     * 发送mq更新考试记录
     *
     * @param recordId
     * @param timestamp
     */
    @Override
    public void sendExamRecordDataSaveMq(Long recordId, Long timestamp) {
        try {
//            if (Objects.nonNull(ServletUtil.getRequest())) {
//                ExamRecordCacheUtil.setClientCurrentIp(recordId, IpUtil.getRemoteIp(ServletUtil.getRequest()));
//            }
            ExamRecordCacheUtil.setUpdateTime(recordId, timestamp);
            MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name(), recordId.toString(), timestamp);
            mqDtoService.assembleSendAsyncMsg(mqDto);
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    /**
     * 发送mq更新考试预警数量记录
     *
     * @param recordId
     */
    @Override
    public void sendExamRecordDataUpdateWarningCountMq(Long recordId) {
        try {
            MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name(), recordId.toString(), SystemConstant.WARNING_COUNT_UPDATE, System.currentTimeMillis());
            mqDtoService.assembleSendAsyncMsg(mqDto);
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    /**
     * 发送mq更新考试预警未读数量
     *
     * @param recordId
     * @param number
     */
    @Override
    public void sendExamRecordDataUpdateWarningUnreadMq(Long recordId, Integer number) {
        try {
            Map<String, Object> properties = new HashMap<>();
            properties.put(SystemConstant.NUMBER, number);
            MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name(), recordId.toString(), SystemConstant.WARNING_UNREAD_UPDATE, System.currentTimeMillis());
            mqDto.setProperties(properties);
            mqDtoService.assembleSendAsyncMsg(mqDto);
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    /**
     * 发送mq更新考试记录
     *
     * @param recordId
     * @param timestamp
     */
    @Override
    public void sendExamRecordDataSaveMqNotIp(Long recordId, Long timestamp) {
        ExamRecordCacheUtil.setUpdateTime(recordId, timestamp);
        MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.EXAM_RECORD_UPDATE.name(), recordId.toString(), timestamp);
        mqDtoService.assembleSendAsyncMsg(mqDto);
    }

    /**
     * 查询实时监控台视频列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param paperDownload
     * @param status
     * @param name
     * @param identity
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param cameraMonitorStatus
     * @param screenMonitorStatus
     * @param mobileFirstMonitorStatus
     * @param mobileSecondMonitorStatus
     * @param userId
     * @param orgId
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IPage<InvigilateListVideoBean> invigilatePageListVideo(IPage<Map> iPage, Long examId, Long examActivityId,
                                                                  String roomCode, Integer paperDownload, String status, String name, String identity,
                                                                  Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String cameraMonitorStatus,
                                                                  String screenMonitorStatus,
                                                                  String mobileFirstMonitorStatus,
                                                                  String mobileSecondMonitorStatus, Long userId, Long orgId) {
        return tOeExamRecordMapper
                .invigilatePageListVideo(iPage, examId, examActivityId, roomCode, paperDownload, status, name, identity,
                        minWarningCount, maxWarningCount, clientWebsocketStatus, cameraMonitorStatus, screenMonitorStatus, mobileFirstMonitorStatus, mobileSecondMonitorStatus, userId, orgId);
    }

    /**
     * 实时监控台视频随机列表
     *
     * @param examId
     * @param userId
     * @param randomNum
     * @param orgId
     * @return
     */
    @Override
    public List<InvigilateListVideoBean> invigilatePageListVideoRandom(Long examId, Long userId, Integer randomNum,
                                                                       Long orgId) {
        return tOeExamRecordMapper.invigilatePageListVideoRandom(examId, userId, randomNum, orgId);
    }

    /**
     * 查询在线巡考列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param status
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param userId
     * @param orgId
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IPage<InvigilateListPatrolBean> invigilatePagePatrolList(IPage<Map> iPage, Long examId, Long examActivityId,
                                                                    String roomCode, String status, String name, String identity, Integer minMultipleFaceCount,
                                                                    Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount,
                                                                    Integer maxWarningCount, String clientWebsocketStatus, Long userId, Long orgId) {
        return tOeExamRecordMapper
                .invigilatePagePatrolList(iPage, examId, examActivityId, roomCode, status, name, identity,
                        minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
                        minWarningCount, maxWarningCount, clientWebsocketStatus, userId, orgId);
    }

    /**
     * 预警提醒列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param approveStatus
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param userId
     * @param orgId
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IPage<InvigilateListWarningBean> invigilatePageWarningList(IPage<Map> iPage, Long examId,
                                                                      Long examActivityId, String roomCode, Integer approveStatus, String name, String identity,
                                                                      Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount,
                                                                      Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, Long userId, Long orgId) {
        return tOeExamRecordMapper
                .invigilatePageWarningList(iPage, examId, examActivityId, roomCode, approveStatus, name, identity,
                        minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
                        minWarningCount, maxWarningCount, userId, orgId);
    }

    /**
     * 预警提醒清除未阅列表
     *
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param approveStatus
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param userId
     * @param orgId
     * @return
     */
    @Override
    public Integer approveStatusListUpdate(Long examId, Long examActivityId, String roomCode, Integer approveStatus,
                                           String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
                                           Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
                                           Long userId, Long orgId) {
        return tOeExamRecordMapper
                .approveStatusListUpdate(examId, examActivityId, roomCode, approveStatus, name, identity,
                        minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount,
                        minWarningCount, maxWarningCount, userId, orgId);
    }

    /**
     * 进度查询列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param name
     * @param identity
     * @param status
     * @param userId
     * @param orgId
     * @param breachStatus
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IPage<InvigilateListProgressBean> invigilatePageProgressList(IPage<Map> iPage, Long examId,
                                                                        Long examActivityId, String roomCode, String courseCode, String name, String identity,
                                                                        Integer status, Long userId,
                                                                        Long orgId, Integer breachStatus) {
        IPage<InvigilateListProgressBean> invigilateListProgressBeanIPage = tOeExamRecordMapper.invigilatePageProgressList(iPage, examId, examActivityId, roomCode, courseCode, name, identity, status, userId, orgId, breachStatus);
        for (InvigilateListProgressBean t : invigilateListProgressBeanIPage.getRecords()) {
            if (Objects.isNull(t.getTencentVideoUrl())) {
                String videoSource = tmTencentVideoMessageService.videoSourceQuery(t.getExamRecordId());
                t.setVideoSource(videoSource);
            }
            if (Objects.nonNull(t.getTencentVideoUrl()) && !Objects.equals(t.getTencentVideoUrl().trim(), "")) {
                List<TEStudentMonitorRecordDto> monitorRecordList = GsonUtil.fromJson(t.getTencentVideoUrl(), new TypeToken<List<TEStudentMonitorRecordDto>>() {
                }.getType());
                Set<MonitorVideoSourceEnum> set = new LinkedHashSet<>(monitorRecordList.size());
                for (TEStudentMonitorRecordDto teStudentMonitorRecordDto : monitorRecordList) {
                    MonitorVideoSourceEnum monitorVideoSourceEnum = SystemConstant.getMonitorRecordVideoSource(teStudentMonitorRecordDto.getVideoSource());
                    if (Objects.nonNull(monitorVideoSourceEnum)) {
                        set.add(monitorVideoSourceEnum);
                    }
                }
                t.setMonitorRecord(set.toString().replaceAll("\\[", "").replaceAll("\\]", "").replaceAll(", ", ",").trim());
            } else if (Objects.nonNull(t.getVideoSource()) && !Objects.equals(t.getVideoSource().trim(), "")) {
                String[] strs = t.getVideoSource().split(",");
                Set<MonitorVideoSourceEnum> set = new LinkedHashSet<>(strs.length);
                for (String s : strs) {
                    MonitorVideoSourceEnum monitorVideoSourceEnum = SystemConstant.getMonitorRecordVideoSource(s);
                    if (Objects.nonNull(monitorVideoSourceEnum)) {
                        set.add(monitorVideoSourceEnum);
                    }
                }
                t.setMonitorRecord(set.toString().replaceAll("\\[", "").replaceAll("\\]", "").replaceAll(", ", ",").trim());
            }
        }
        return invigilateListProgressBeanIPage;
    }

    /**
     * 进度查询列表导出
     *
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param name
     * @param identity
     * @param status
     * @param userId
     * @param orgId
     * @param breachStatus
     * @return
     */
    @Override
    public List<InvigilateListProgressExcelBean> invigilatePageProgressListExport(Long examId, Long examActivityId,
                                                                                  String roomCode, String courseCode, String name, String identity,
                                                                                  Integer status, Long userId, Long orgId, Integer breachStatus) {
        return tOeExamRecordMapper.invigilatePageProgressListExport(examId, examActivityId, roomCode, courseCode, name, identity, status, userId, orgId, breachStatus);
    }

    /**
     * 监考明细管理列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param status
     * @param breachStatus
     * @param finishType
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param userId
     * @param orgId
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IPage<InvigilateListHistoryBean> invigilatePageListHistory(IPage<Map> iPage, Long examId,
                                                                      Long examActivityId, String roomCode, String courseCode, String status, Integer breachStatus,
                                                                      String finishType, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount,
                                                                      Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount,
                                                                      Long userId, Long orgId) {
        return tOeExamRecordMapper
                .invigilatePageListHistory(iPage, examId, examActivityId, roomCode, courseCode, status, breachStatus,
                        finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount,
                        maxExceptionCount, minWarningCount, maxWarningCount, userId, orgId);
    }

    /**
     * 监考明细管理列表导出
     *
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param status
     * @param breachStatus
     * @param finishType
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param userId
     * @param orgId
     * @return
     */
    @Override
    public List<InvigilateListHistoryBean> invigilatePageListHistoryExport(Long examId,
                                                                           Long examActivityId,
                                                                           String roomCode,
                                                                           String courseCode,
                                                                           String status,
                                                                           Integer breachStatus,
                                                                           String finishType,
                                                                           String name,
                                                                           String identity,
                                                                           Integer minMultipleFaceCount,
                                                                           Integer maxMultipleFaceCount,
                                                                           Integer minExceptionCount,
                                                                           Integer maxExceptionCount,
                                                                           Integer minWarningCount,
                                                                           Integer maxWarningCount,
                                                                           Long userId,
                                                                           Long orgId) {
        return tOeExamRecordMapper
                .invigilatePageListHistoryExport(examId, examActivityId, roomCode, courseCode, status, breachStatus,
                        finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount,
                        maxExceptionCount, minWarningCount, maxWarningCount, userId, orgId);
    }

    /**
     * 重新算分
     */
    @Transactional
    @Override
    public void calculateScore(Long recordId) {
        TOeExamRecord rec = this.getById(recordId);

        ExamPaperCacheBean ep = examPaperService.getExamPaperCacheBean(rec.getPaperId());
        if (rec.getHasAnswerFile().intValue() == 0 && StringUtils.isNotBlank(ep.getAnswerPath())) {
            tOeExamRecordMapper.updateHasAnswerFile(recordId, 1);
        }

        Map<String, ObjectiveAnswerCacheBean> map = examPaperService.getObjectiveAnswerCacheBean(rec.getPaperId());
        if (map == null || map.size() == 0) {
            log.info("no ObjectiveAnswerCacheBean for calculateObjectiveScore recordId:" + recordId);
            return;
        }
        QueryWrapper<TOeExamAnswer> wp = new QueryWrapper<>();
        wp.lambda().eq(TOeExamAnswer::getExamRecordId, recordId);
        List<TOeExamAnswer> answers = examAnswerService.list(wp);
        if (answers == null || answers.size() == 0) {
            log.info("no ObjectiveAnswer for calculateObjectiveScore recordId:" + recordId);
            return;
        }
        BigDecimal tatolscore = BigDecimal.valueOf(0.0);
        for (TOeExamAnswer answer : answers) {
            Double score = calculateScoreForQuestion(answer, map);
            if (score != null) {
                tatolscore = tatolscore.add(BigDecimal.valueOf(score));
            }
        }

        // 设置客观分总分
        tOeExamRecordMapper.updateObjectiveScore(recordId, tatolscore.doubleValue());
    }

    private Double calculateScoreForQuestion(TOeExamAnswer answer, Map<String, ObjectiveAnswerCacheBean> map) {
        Long recordId = answer.getExamRecordId();
        Integer mainNumber = answer.getMainNumber();
        Integer subNumber = answer.getSubNumber();
        Integer subIndex = answer.getSubIndex();
        String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
        ObjectiveAnswerCacheBean cb = map.get(key);
        if (cb == null) {
            log.info("not a objective question  recordId:" + answer.getExamRecordId() + " key:" + key);
            return null;
        }
        if (cb.getStructType().intValue() == 1) {
            if (Objects.nonNull(cb.getChoiceAnswer()) && cb.getChoiceAnswer().size() > 0) {
                if (checkSingleChoice(answer.getAnswer(), cb.getChoiceAnswer())) {
                    answer.setScore(cb.getScore());
                } else {
                    answer.setScore(0.0);
                }
            } else {
                answer.setScore(0.0);
            }
        }
        if (cb.getStructType().intValue() == 2) {
            ExamCacheBean ec = examService.getExamCacheBean(tOeExamRecordMapper.selectById(recordId).getExamId());
            if (Objects.nonNull(cb.getChoiceAnswer()) && cb.getChoiceAnswer().size() > 0) {
                int r = checkMultipleChoice(answer.getAnswer(), cb.getChoiceAnswer());
                if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.EQUAL)) {// 全对给分
                    if (r == 1) {
                        answer.setScore(cb.getScore());
                    } else {
                        answer.setScore(0.0);
                    }
                }
                if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.PARTIAL)) {// 漏选半分
                    if (r == 1) {
                        answer.setScore(cb.getScore());
                    } else if (r == 0) {
                        BigDecimal b = BigDecimal.valueOf(cb.getScore()).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_UP);
                        answer.setScore(b.doubleValue());
                    } else {
                        answer.setScore(0.0);
                    }
                }
            } else {
                answer.setScore(0.0);
            }
        }
        if (cb.getStructType().intValue() == 3) {
            if (StringUtils.isNotBlank(answer.getAnswer())) {
                String answerStr = answer.getAnswer().replaceAll("\n", "");
                if (Objects.nonNull(answerStr) && Objects.nonNull(cb.getBoolAnswer()) && Boolean.parseBoolean(answerStr) == cb.getBoolAnswer().booleanValue()) {
                    answer.setScore(cb.getScore());
                } else {
                    answer.setScore(0.0);
                }
            }
        }
        examAnswerService.saveOrUpdate(answer);
        return answer.getScore();
    }

    /**
     * 获取考试记录数
     *
     * @param examId
     * @return
     */
    @Override
    public Long getCountByExamId(Long examId) {
        return tOeExamRecordMapper.getCountByExamId(examId);
    }

    /**
     * 批量获取考试记录
     *
     * @param examId
     * @param startId
     * @return
     */
    @Override
    public List<TOeExamRecord> getListByExamIdAndStartId(Long examId, Long startId) {
        return tOeExamRecordMapper.getListByExamIdAndStartId(examId, startId);
    }

    @Override
    public TOeExamRecord findOneByPaperId(Long paperId) {
        return tOeExamRecordMapper.findOneByPaperId(paperId);
    }

    @Override
    public TOeExamRecord findOneByExamId(Long examId, ExamRecordStatusEnum status) {
        return tOeExamRecordMapper.findOneByExamId(examId, status == null ? null : status.name());
    }

    @Override
    public List<OpenRecordNeedMarkBean> findExamRecordNeedMark(Long examStudentId) {
        return tOeExamRecordMapper.findExamRecordNeedMark(examStudentId);
    }

    /**
     * 寻找客观分最高
     *
     * @param examId
     * @param examStudentId
     * @return
     */
    @Override
    public List<TOeExamRecord> findMaxObjectiveScore(Long examId, Long examStudentId) {
        ExamCacheBean examCacheBean = examService.getExamCacheBean(examId);
        return tOeExamRecordMapper.findMaxObjectiveScore(examId, examStudentId, examCacheBean.getExamStudentBreachPush());
    }

    /**
     * 查询成绩
     *
     * @param markResultDto
     * @param recordSelectStrategyEnum
     * @return
     */
    @Override
    public TOeExamRecord findMarkResult(MarkResultDto markResultDto,
                                        RecordSelectStrategyEnum recordSelectStrategyEnum) {
        TOeExamRecord tOeExamRecord = null;
        Double sumScore = null;
        //客观分最高
        if (Objects.nonNull(recordSelectStrategyEnum) && Objects
                .equals(recordSelectStrategyEnum, RecordSelectStrategyEnum.HIGHEST_OBJECTIVE_SCORE)) {
            List<TOeExamRecord> tOeExamRecordList = this
                    .findMaxObjectiveScore(markResultDto.getExamId(), markResultDto.getExamStudentId());
            if (Objects.nonNull(tOeExamRecordList) && tOeExamRecordList.size() > 0) {
                Long paperId = tOeExamRecordList.get(0).getPaperId();
                ExamPaperCacheBean paperCacheBean = examPaperService.getExamPaperCacheBean(paperId);
                boolean isHasSubjective = true;
                if (paperCacheBean.getTotalSubjectiveScore().intValue() == 0) {
                    isHasSubjective = false;
                }
                //是否有主观题
                if (!isHasSubjective) {
                    for (TOeExamRecord t : tOeExamRecordList) {
                        if (Objects.isNull(tOeExamRecord)) {
                            tOeExamRecord = t;
                        } else {
                            if (Objects.nonNull(t.getObjectiveScore()) && Objects
                                    .nonNull(tOeExamRecord.getObjectiveScore())
                                    && t.getObjectiveScore().doubleValue() >= tOeExamRecord.getObjectiveScore()
                                    .doubleValue() && Objects.nonNull(t.getFinishTime()) && Objects
                                    .nonNull(tOeExamRecord.getFinishTime())
                                    && t.getFinishTime().longValue() > tOeExamRecord.getFinishTime().longValue()) {
                                tOeExamRecord = t;
                            }
                        }
                    }
                } else {
                    //总分最高选取
                    tOeExamRecord = getMaxSumScore(tOeExamRecord, sumScore, markResultDto);
                }
            }
        }
        //最后一次提交
        else if (Objects.nonNull(recordSelectStrategyEnum) && Objects
                .equals(recordSelectStrategyEnum, RecordSelectStrategyEnum.LATEST)) {
            List<TOeExamRecord> tOeExamRecordList = this
                    .findMaxObjectiveScore(markResultDto.getExamId(), markResultDto.getExamStudentId());
            for (TOeExamRecord t : tOeExamRecordList) {
                if (Objects.isNull(tOeExamRecord)) {
                    tOeExamRecord = t;
                } else {
                    if (Objects.nonNull(t.getFinishTime()) && Objects.nonNull(tOeExamRecord.getFinishTime())
                            && t.getFinishTime().longValue() > tOeExamRecord.getFinishTime().longValue()) {
                        tOeExamRecord = t;
                    }
                }
            }
        }
        //全部阅卷后取最高分
        else if (Objects.nonNull(recordSelectStrategyEnum) && Objects
                .equals(recordSelectStrategyEnum, RecordSelectStrategyEnum.HIGHEST_TOTAL_SCORE)) {
            tOeExamRecord = getMaxSumScore(tOeExamRecord, sumScore, markResultDto);
        }
        return tOeExamRecord;
    }

    /**
     * 设置断点信息
     *
     * @param recordId
     */
    @Override
    @Transactional
    public Boolean setExamBreak(Long recordId) {
        boolean lock = false;
        for (int i = 0; i < SystemConstant.MAX_EXAM_STATUS_COUNT; i++) {
            lock = redisUtil.lock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId,
                    SystemConstant.REDIS_LOCK_EXAM_STATUS_TIME_OUT);
            if (lock) {
                try {
                    Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
                    Long breakId = uidUtil.getId();
                    Long lastBreakTimeNow = System.currentTimeMillis();
                    ExamRecordCacheUtil.setLastBreakId(recordId, breakId);
                    ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF, lastBreakTimeNow);
                    ExamRecordCacheUtil.setLastBreakTime(recordId, lastBreakTimeNow);
                    ExamRecordCacheUtil.setAlreadyBreakCount(recordId, 1);

                    Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
                    Long examId = ExamRecordCacheUtil.getExamId(recordId);
                    if (Objects.isNull(examStudentId)) {
                        TOeExamRecord tOeExamRecord = this.getById(recordId);
                        Optional.ofNullable(tOeExamRecord).orElseThrow(() -> new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_RECORD));
                        examStudentId = tOeExamRecord.getExamStudentId();
                        examId = tOeExamRecord.getExamId();
                    }
                    ExamStudentCacheBean es = teExamStudentService.getExamStudentCacheBean(examStudentId);
                    ExamingDataCacheUtil.setUnFinishedRecordId(es.getStudentId(), recordId);
                    ExamRecordStatusEnum examRecordStatusEnum = ExamRecordCacheUtil.getStatus(recordId);
                    if (Objects.nonNull(examRecordStatusEnum) && examRecordStatusEnum == ExamRecordStatusEnum.BREAK_OFF) {
                        this.sendExamRecordDataSaveMq(recordId, lastBreakTimeNow);
//                        //考试断点异常原因 发送mq start
                        MqDto mqDtoBreak = new MqDto(mqUtil.getTopic(), MqTagEnum.EXAM_BREAK.name(),
                                ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId),
                                String.valueOf(recordId));
                        mqDtoService.assembleSendAsyncMsg(mqDtoBreak);
//                        //考试断点异常原因 发送mq end

                        ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
                        Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ?
                                0 :
                                ec.getBreakExpireSeconds();
                        sendExamBreakMqDelayMsg(breakExpireSeconds, recordId);
                        //更新场次-考试记录缓存
                    } else {
                        throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ALREADY_UPDATE);
                    }
                    break;
                } catch (Exception e) {
                    log.error(SystemConstant.LOG_ERROR, e);
                    if (e instanceof BusinessException) {
                        throw new BusinessException(e.getMessage());
                    } else {
                        throw new RuntimeException(e);
                    }
                } finally {
                    if (Objects.nonNull(recordId)) {
                        redisUtil.releaseLock(SystemConstant.REDIS_LOCK_EXAM_STATUS_PREFIX + recordId);
                    }
                }
            } else {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if (!lock) {
            tgErrorService.saveExamTgError(recordId, "setExamBreak");
        }
        return lock;
    }

    /**
     * 断点逻辑
     *
     * @param recordId
     * @return
     */
    @Override
    public Boolean examBreakLogic(Long recordId, boolean setBreak) {
        Boolean finished = false;
        try {
            if (setBreak) {
                this.setExamBreak(recordId);
            }
            Long examId = ExamRecordCacheUtil.getExamId(recordId);
            Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
            if (Objects.isNull(examStudentId)) {
                TOeExamRecord tOeExamRecord = this.getById(recordId);
                Optional.ofNullable(tOeExamRecord).orElseThrow(() -> new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_RECORD));
                examStudentId = tOeExamRecord.getExamStudentId();
                examId = tOeExamRecord.getExamId();
            }
            ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
            ExamCacheBean ec = examService.getExamCacheBean(examId);//考试缓存
            Long lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
            Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ? 0 : ec.getBreakExpireSeconds();
            Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
            Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
            Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
            if (ec.getBreakResumeCount().intValue() == 0 && alreadyBreakCount.intValue() == 0) {
                leftBreakResumeCount = 1;
            }
            if (Objects.nonNull(lastBreakTime)
                    && (System.currentTimeMillis() - lastBreakTime.longValue()) / 1000 >= breakExpireSeconds) {
                finished = true;
                examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
            } else {
                if (leftBreakResumeCount.intValue() <= 0) {
                    finished = true;
                    examService.finish(examStudentCacheBean.getStudentId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
                }
            }
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof BusinessException) {
                throw new BusinessException(e.getMessage());
            } else {
                throw new RuntimeException(e);
            }
        }
        return finished;
    }

    /**
     * 根据orgId查找 在线人数/已待考/考试人数
     *
     * @param orgId
     * @return
     */
    @Override
    public List<Long> findByOrgId(Long orgId) {
        return tOeExamRecordMapper.findByOrgId(orgId);
    }

    /**
     * 查找监控消息
     *
     * @param examId
     * @param roomCodeSet
     * @param examActivityId
     * @return
     */
    @Override
    public List<CountStopBean> findByStopInfo(Long examId, Set<String> roomCodeSet, Long examActivityId) {
        return tOeExamRecordMapper.findByStopInfo(examId, roomCodeSet, examActivityId);
    }

    /**
     * 查找在线消息
     *
     * @param examId
     * @param roomCodeSet
     * @param examActivityId
     * @return
     */
    @Override
    public List<Integer> findByOnlineInfo(Long examId, Set<String> roomCodeSet, Long examActivityId) {
        return tOeExamRecordMapper.findByOnlineInfo(examId, roomCodeSet, examActivityId);
    }

    /**
     * 管理员报表
     *
     * @param orgId
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @return
     */
    @Override
    public List<Integer> findByViewAdmin(Long orgId, Long examId, Long examActivityId, String roomCode, String courseCode, InvigilateMonitorStatusEnum monitorStatus) {
        return tOeExamRecordMapper.findByViewAdmin(orgId, examId, examActivityId, roomCode, courseCode, Objects.nonNull(monitorStatus) ? monitorStatus.name() : null);
    }

    /**
     * 考试明细查询接口
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param courseCode
     * @param roomCode
     * @param finishType
     * @param name
     * @param identity
     * @return
     */
    @Override
    public IPage<ExamRecordDetailBean> examRecordDetailQuery(IPage<Map> iPage, Long examId, Long examActivityId, String courseCode, String roomCode, String finishType, String name, String identity) {
        return tOeExamRecordMapper.examRecordDetailQuery(iPage, examId, examActivityId, courseCode, roomCode, finishType, name, identity);
    }

    /**
     * 考试明细查询接口
     *
     * @param examId
     * @param courseCodeList
     * @param identityList
     * @return
     */
    @Override
    public List<String> examRecordDetailQuery(Long examId, List<String> courseCodeList, List<String> identityList) {
        return tOeExamRecordMapper.examRecordDetailListQuery(examId, courseCodeList, identityList);
    }

    /**
     * 查看试卷
     *
     * @param examRecordId
     * @return
     */
    @Override
    public ExamRecordPaperViewBean examRecordPaperView(Long examRecordId) throws IOException {
        TOeExamRecord tOeExamRecord = SystemConstant.getExamRecord(examRecordId);

        ExamPaperCacheBean teExamPaper = teExamPaperService.getExamPaperCacheBean(tOeExamRecord.getPaperId());
        Optional.ofNullable(teExamPaper).orElseThrow(() -> new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_PAPER));

        ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(tOeExamRecord.getExamStudentId());
        Optional.ofNullable(examStudentCacheBean).orElseThrow(() -> new BusinessException(ExceptionResultEnum.NOT_FOUND_EXAM_STUDENT));

        QueryWrapper<TSyncExamStudentScore> tSyncExamStudentScoreQueryWrapper = new QueryWrapper<>();
        tSyncExamStudentScoreQueryWrapper.lambda().eq(TSyncExamStudentScore::getExamNumber, String.valueOf(examRecordId))
                .eq(TSyncExamStudentScore::getStatus, 1);
        TSyncExamStudentScore tSyncExamStudentScore = tSyncExamStudentScoreService.getOne(tSyncExamStudentScoreQueryWrapper);

        JSONObject paperStructJson = null, answerJson = null, randomPaperStructJson = null;
        String examStudentAnswerJson = null;
        if (Objects.nonNull(teExamPaper.getPaperViewPath())) {
            //获取试卷结构json
            paperStructJson = JSONObject.parseObject(new String(ossUtil.download(false, teExamPaper.getPaperViewPath()), StandardCharsets.UTF_8));
        }
        if (Objects.nonNull(teExamPaper.getAnswerPath())) {
            //获取答案结构json
            answerJson = JSONObject.parseObject(new String(ossUtil.download(false, teExamPaper.getAnswerPath()), StandardCharsets.UTF_8));
        }
        if (Objects.nonNull(tOeExamRecord.getPaperStructPath())) {
            //获取乱序后的试卷结构json
            randomPaperStructJson = JSONObject.parseObject(new String(ossUtil.download(false, tOeExamRecord.getPaperStructPath()), StandardCharsets.UTF_8));
        }

        QueryWrapper<TOeExamAnswer> tOeExamAnswerQueryWrapper = new QueryWrapper<>();
        tOeExamAnswerQueryWrapper.lambda().eq(TOeExamAnswer::getExamRecordId, tOeExamRecord.getId())
                .orderByAsc(TOeExamAnswer::getMainNumber, TOeExamAnswer::getSubNumber, TOeExamAnswer::getSubIndex);
        List<TOeExamAnswer> tOeExamAnswerList = tOeExamAnswerService.list(tOeExamAnswerQueryWrapper);

        if (Objects.nonNull(tOeExamAnswerList) && tOeExamAnswerList.size() > 0) {
            List<ExamRecordAnswerBean> examRecordAnswerBeanList = GsonUtil.fromJson(JacksonUtil.parseJson(tOeExamAnswerList), new TypeToken<List<ExamRecordAnswerBean>>() {
            }.getType());
            examStudentAnswerJson = JacksonUtil.parseJson(examRecordAnswerBeanList);
        }
        return new ExamRecordPaperViewBean(tOeExamRecord.getId(),
                examStudentCacheBean.getName(),
                examStudentCacheBean.getIdentity(),
                examStudentCacheBean.getCourseCode(),
                examStudentCacheBean.getCourseName(),
                examStudentCacheBean.getRoomCode(),
                examStudentCacheBean.getRoomName(),
                tOeExamRecord.getFinishType(),
                Objects.nonNull(paperStructJson) ? paperStructJson.toJSONString() : null,
                Objects.nonNull(answerJson) ? answerJson.toJSONString() : null,
                examStudentAnswerJson,
                Objects.nonNull(randomPaperStructJson) ? randomPaperStructJson.toJSONString() : null,
                Objects.nonNull(tSyncExamStudentScore) && Objects.nonNull(tSyncExamStudentScore.getSubjectiveScoreDetail()) ? tSyncExamStudentScore.getSubjectiveScoreDetail() : null);
    }

    /**
     * 查询没有视频地址的考试记录
     *
     * @param examId
     * @return
     */
    @Override
    public List<TOeExamRecord> findExamRecordByNotVideoUrl(Long examId) {
        return tOeExamRecordMapper.findExamRecordByNotVideoUrl(examId);
    }

    /**
     * 根据考试批次id和场次id查找考试数据
     *
     * @param examId
     * @param examActivityId
     * @return
     */
    @Override
    public List<TOeExamRecord> findExamRecordByExamIdAndExamActivityId(Long examId, Long examActivityId) {
        return tOeExamRecordMapper.findExamRecordByExamIdAndExamActivityId(examId, examActivityId);
    }

    /**
     * 根据考试批次id或考试场次id查找
     *
     * @param examId
     * @param examActivityId
     * @return
     */
    @Override
    public Integer findByExamIdOrExamActivityIdCount(Long examId, Long examActivityId) {
        return tOeExamRecordMapper.findByExamIdOrExamActivityIdCount(examId, examActivityId);
    }

    /**
     * 获取所有待阅卷的考试记录
     *
     * @param examId
     * @param examPaperIdSet
     * @param many
     * @param examStudentBreachPush
     * @return
     */
    @Override
    public List<TOeExamRecord> findExamRecordNeedMarkAll(Long examId, Set<Long> examPaperIdSet, Boolean many, Boolean examStudentBreachPush) {
        return tOeExamRecordMapper.findExamRecordNeedMarkAll(examId, examPaperIdSet, many, examStudentBreachPush);
    }

    /**
     * 修正作答记录缓存到数据库
     *
     * @param examId
     * @param examActivityId
     * @param status
     * @return
     */
    @Override
    public List<TOeExamRecord> persistedAnswerBatch(Long examId, Long examActivityId, ExamRecordStatusEnum status) {
        return tOeExamRecordMapper.persistedAnswerBatch(examId, examActivityId, Objects.nonNull(status) ? status.name() : null);
    }

    /**
     * 考试记录数据持久化
     *
     * @param recordId
     */
    @Override
    @Transactional
    public void saveDataByCachePersistedAnswer(Long recordId) {
        //保存作答
        Map<String, ExamStudentAnswerCacheBean> answerMap = redisUtil
                .getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
        if (!CollectionUtils.isEmpty(answerMap)) {
            List<TOeExamAnswer> tOeExamAnswerList = new ArrayList<>(answerMap.size());
            for (ExamStudentAnswerCacheBean answerCache : answerMap.values()) {
                TOeExamAnswer answer = new TOeExamAnswer();
                BeanUtils.copyProperties(answerCache, answer);
                answer.setId(uidUtil.getId());
                answer.setExamRecordId(recordId);
                tOeExamAnswerList.add(answer);
            }
            examAnswerService.saveOrUpdateBatch(tOeExamAnswerList);
        }
    }

    /**
     * 取最高分
     *
     * @param tOeExamRecord
     * @param sumScore
     * @param markResultDto
     * @return
     */
    private TOeExamRecord getMaxSumScore(TOeExamRecord tOeExamRecord, Double sumScore, MarkResultDto markResultDto) {
        Long examId = null, examStudentId = null;
        if (Objects.isNull(sumScore)) {
            sumScore = markResultDto.getSumScore();
            examId = markResultDto.getExamId();
            examStudentId = markResultDto.getExamStudentId();
        } else {
            if (Objects.nonNull(markResultDto.getSumScore()) && markResultDto.getSumScore().doubleValue() >= sumScore
                    .doubleValue()) {
                sumScore = markResultDto.getSumScore();
                examId = markResultDto.getExamId();
                examStudentId = markResultDto.getExamStudentId();
            }
        }
        if (Objects.nonNull(sumScore)) {
            List<TOeExamRecord> tOeExamRecordList = this.findMaxObjectiveScore(examId, examStudentId);
            if (Objects.nonNull(tOeExamRecordList) && tOeExamRecordList.size() > 0) {
                tOeExamRecord = tOeExamRecordList.get(0);
            }
        }
        return tOeExamRecord;
    }

    /**
     * 发送断点延时mq消息
     *
     * @param breakExpireSeconds
     * @param recordId
     */
    private void sendExamBreakMqDelayMsg(Integer breakExpireSeconds, Long recordId) {
        if (breakExpireSeconds.intValue() > 0) {
            List<String> list = SystemConstant.mqDelayLevelList.subList(4, 16);
            String level = null;
            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;
                        }
                    }
                }
            }
            Map<String, Object> tranMap = mqDtoService.buildMqDelayMsg(level);
            tranMap.put(SystemConstant.RECORD_ID, recordId);
            //考试断点延时消息 发送mq start
            MqDto mqDtoBreakDelay = new MqDto(mqUtil.getTopic(),
                    MqTagEnum.EXAM_BREAK_DELAY.name(), MqTagEnum.EXAM_BREAK_DELAY, MqTagEnum.EXAM_BREAK_DELAY,
                    String.valueOf(recordId), tranMap, String.valueOf(recordId));
            mqDtoService.assembleSendAsyncDelayMsg(mqDtoBreakDelay);
            //考试断点延时消息 发送mq end
        }
    }

    /**
     * 修正考试状态
     *
     * @return
     */
    @Override
    public List<TOeExamRecord> updateExamStatus() {
        QueryWrapper<TOeExamRecord> tOeExamRecordQueryWrapper = new QueryWrapper<>();
        tOeExamRecordQueryWrapper.lambda().isNotNull(TOeExamRecord::getFirstStartTime)
                .isNotNull(TOeExamRecord::getFinishTime)
                .eq(TOeExamRecord::getStatus, ExamRecordStatusEnum.FINISHED);
        return this.list(tOeExamRecordQueryWrapper);
    }

    /**
     * 清除考试缓存
     *
     * @param examIds
     * @return
     */
    @Override
    public List<Map> cleanExamFirstPrepareCache(Set<Long> examIds) {
        return tOeExamRecordMapper.cleanExamFirstPrepareCache(examIds);
    }

    /**
     * 数据统计
     *
     * @return
     */
    @Override
    public List<Long> dataCount() {
        TEOrgSummary teOrgSummary = themisCacheService.getOrgSummaryCache(0L);
        if (Objects.isNull(teOrgSummary)) {
            teOrgSummaryService.orgSummary(0L, null);//第一次进来没数据，强制统计一次
            teOrgSummary = themisCacheService.getOrgSummaryCache(0L);
        }
        List<Long> list = new ArrayList<>();
        list.add(Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getOnlineCount()) ? Long.parseLong(teOrgSummary.getOnlineCount().toString()) : 0L);
        list.add(Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getExamCount()) ? Long.parseLong(teOrgSummary.getExamCount().toString()) : 0L);
        list.add(Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getFinishCount()) ? Long.parseLong(teOrgSummary.getFinishCount().toString()) : 0L);
        list.add(Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getFinishStudentCount()) ? Long.parseLong(teOrgSummary.getFinishStudentCount().toString()) : 0L);
        return list;
    }

    /**
     * 机构数据统计
     *
     * @return
     */
    @Override
    public List<OrgDataCountBean> orgDataCount() {
        Set<String> examIdSetSet = redisUtil.getHashKeys(SystemConstant.CURRENT_EXAM_LIST_MAP_CACHE);
        if (!CollectionUtils.isEmpty(examIdSetSet)) {
            Set<Long> examIdSet = examIdSetSet.stream().map(s -> Long.parseLong(s)).collect(Collectors.toSet());
            LinkedMultiValueMap<Long, Long> orgExamIdMap = tbOrgService.mergeOrgId(examIdSet);
            if (Objects.nonNull(orgExamIdMap) && orgExamIdMap.size() > 0) {
                List<OrgDataCountBean> orgDataCountBeanList = new ArrayList<>();
                //统计机构信息
                orgExamIdMap.forEach((k, v) -> {
                    TEOrgSummary teOrgSummary = themisCacheService.getOrgSummaryCache(k);
                    if (Objects.nonNull(teOrgSummary) && ((Objects.nonNull(teOrgSummary.getExamCount()) &&
                            teOrgSummary.getExamCount().intValue() > 0)
                            || (Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getOnlineCount()) &&
                            teOrgSummary.getOnlineCount().intValue() > 0))) {
                        TBOrg tbOrg = themisCacheService.addOrgCache(k);
                        orgDataCountBeanList.add(new OrgDataCountBean(tbOrg.getCode(), tbOrg.getName(), Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getOnlineCount()) ? teOrgSummary.getOnlineCount() : 0, Objects.nonNull(teOrgSummary) && Objects.nonNull(teOrgSummary.getExamCount()) ? teOrgSummary.getExamCount() : 0));
                    }
                });
                return orgDataCountBeanList;
            }
        }
        return null;
    }

    /**
     * 地图数据统计
     *
     * @return
     */
    @Override
    public List<MapDataCountBean> mapDataCount() {
        List<TERegionSummary> teRegionSummaryList = themisCacheService.getRegionSummaryCache();
        if (!CollectionUtils.isEmpty(teRegionSummaryList)) {
            return GsonUtil.fromJson(GsonUtil.toJson(teRegionSummaryList), new TypeToken<List<MapDataCountBean>>() {
            }.getType());
        }
        return new ArrayList<MapDataCountBean>();
    }
}