fuliqi
2024-06-28 16b4725365f3286c2d2a80945e26f35f89b53f24
src/main/java/com/ycl/jxkg/service/impl/ExamServiceImpl.java
@@ -2,14 +2,13 @@
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycl.jxkg.base.Result;
import com.ycl.jxkg.base.SystemCode;
import com.ycl.jxkg.context.WebContext;
import com.ycl.jxkg.domain.entity.Exam;
import com.ycl.jxkg.domain.entity.ExamPaper;
import com.ycl.jxkg.domain.entity.ExamSubmitTemp;
import com.ycl.jxkg.domain.entity.Question;
import com.ycl.jxkg.domain.entity.*;
import com.ycl.jxkg.domain.exam.PaperFixQuestionDTO;
import com.ycl.jxkg.domain.exam.PaperQuestionSettingDTO;
import com.ycl.jxkg.domain.form.AddTimeForm;
@@ -18,7 +17,14 @@
import com.ycl.jxkg.domain.query.ExamQuery;
import com.ycl.jxkg.domain.question.QuestionObject;
import com.ycl.jxkg.domain.vo.*;
import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperEditRequestVO;
import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperMarkAnswerVO;
import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperMarkVO;
import com.ycl.jxkg.domain.vo.student.exam.ExamPaperReadVO;
import com.ycl.jxkg.domain.vo.student.exam.ExamPaperSubmitVO;
import com.ycl.jxkg.enums.DeductTypeEnum;
import com.ycl.jxkg.enums.ExamPaperTypeEnum;
import com.ycl.jxkg.enums.QuestionTypeEnum;
import com.ycl.jxkg.enums.WebsocketCommendEnum;
import com.ycl.jxkg.enums.general.ExamStatusEnum;
import com.ycl.jxkg.enums.general.ExamSubmitTempStatusEnum;
@@ -26,14 +32,18 @@
import com.ycl.jxkg.server.WebsocketServer;
import com.ycl.jxkg.service.ExamPaperService;
import com.ycl.jxkg.service.ExamService;
import com.ycl.jxkg.utils.JsonUtil;
import com.ycl.jxkg.utils.PageUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@@ -55,6 +65,9 @@
    private final ExamPaperMapper examPaperMapper;
    private final ExamPaperService examPaperService;
    private final WebsocketServer websocketServer;
    private final UserMapper userMapper;
    private final ExamPaperScoreMapper examPaperScoreMapper;
    private final ExamPaperScoreDetailMapper examPaperScoreDetailMapper;
    /**
     * 添加
@@ -123,11 +136,11 @@
        IPage<ExamVO> page = PageUtil.getPage(query, ExamVO.class);
        baseMapper.getPage(page, query, webContext.getCurrentUser().getId());
        page.getRecords().stream().forEach(item -> {
            if (! StringUtils.hasText(item.getClassName())) {
            if (!StringUtils.hasText(item.getClassName())) {
                item.setClassName("班级不存在或被删除");
                item.setClassesId(null);
            }
            if (! StringUtils.hasText(item.getExamPaperName())) {
            if (!StringUtils.hasText(item.getExamPaperName())) {
                item.setExamPaperName("试卷不存在或被删除");
                item.setExamPaperId(null);
            }
@@ -165,6 +178,20 @@
        if (Objects.isNull(examPaper)) {
            throw new RuntimeException("试卷不存在");
        }
        // 如果已经参加过考试,直接返回数据
        ExamSubmitTemp hasJoin = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
                .eq(ExamSubmitTemp::getExamId, id)
                .eq(ExamSubmitTemp::getUserId, webContext.getCurrentUser().getId())
                .one();
        if (Objects.nonNull(hasJoin)) {
            StartExamVO startExamVO = new StartExamVO();
            startExamVO.setExamName(exam.getExamName());
            startExamVO.setId(hasJoin.getExamId());
            startExamVO.setTitleList(JSON.parseArray(hasJoin.getExamSubmit(), PaperFixQuestionVO.class));
            startExamVO.setSuggestTime(examPaper.getSuggestTime());
            startExamVO.setDoTime(hasJoin.getDoTime());
            return Result.ok(startExamVO);
        }
        // 将题目转换为可临时保存的题目结构。固定试卷和随序试卷的题目是直接保存到content字段的
        if (ExamPaperTypeEnum.Fixed.getCode().equals(examPaper.getPaperType())
                || ExamPaperTypeEnum.RandomOrder.getCode().equals(examPaper.getPaperType())) {
@@ -176,7 +203,7 @@
            return Result.ok().data(data);
        } else if (ExamPaperTypeEnum.Random.getCode().equals(examPaper.getPaperType())) {
            // 根据随机试卷的配置,随机生成对应题目
            if (! StringUtils.hasText(examPaper.getContent())) {
            if (!StringUtils.hasText(examPaper.getContent())) {
                throw new RuntimeException("试卷配置异常,请联系老师");
            }
            List<PaperQuestionSettingDTO> paperSettingList = JSON.parseArray(examPaper.getContent(), PaperQuestionSettingDTO.class);
@@ -211,11 +238,13 @@
            }
            ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp();
            examSubmitTemp.setExamId(id);
            examSubmitTemp.setStatus(ExamSubmitTempStatusEnum.TEMP);
            //TODO
            examSubmitTemp.setStatus(ExamSubmitTempStatusEnum.temp);
            examSubmitTemp.setExamSubmit(JSON.toJSONString(examData));
            examSubmitTemp.setCreateTime(new Date());
            examSubmitTemp.setUserId(webContext.getCurrentUser().getId());
            examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.TEMP);
            //TODO
            examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.temp);
            examSubmitTempMapper.insert(examSubmitTemp);
            StartExamVO startExamVO = new StartExamVO();
            startExamVO.setExamName(exam.getExamName());
@@ -234,7 +263,7 @@
     * @return
     */
    private List<PaperFixQuestionVO> coverTo(ExamPaper examPaper) {
        if (! StringUtils.hasText(examPaper.getContent())) {
        if (!StringUtils.hasText(examPaper.getContent())) {
            throw new RuntimeException("试卷未配置题目,请联系老师");
        }
        List<PaperFixQuestionDTO> questionWarpList = JSON.parseArray(examPaper.getContent(), PaperFixQuestionDTO.class);
@@ -246,6 +275,12 @@
                DoQuestionVO doQuestionVO = new DoQuestionVO();
                doQuestionVO.setTitle(question.getTitle());
                doQuestionVO.setQuestionType(item.getQuestionType());
                // 填空题需要抹除content(因为是答案)
                if (QuestionTypeEnum.GapFilling.getCode().equals(doQuestionVO.getQuestionType())) {
                    question.getItems().stream().forEach(option -> {
                        option.setContent("");
                    });
                }
                doQuestionVO.setQuestionItemList(question.getItems());
                doQuestionVO.setId(question.getId());
                doQuestionVO.setOriginalFile(question.getOriginalFile());
@@ -295,9 +330,9 @@
     * @return
     */
    @Override
    public Result examSubmit(ExamSubmitVO submitData) {
    public Result examSubmit(StartExamVO submitData) {
        // 校验
        Exam exam = examMapper.selectById(submitData.getExamId());
        Exam exam = examMapper.selectById(submitData.getId());
        if (Objects.isNull(exam)) {
            throw new RuntimeException("该考试不存在");
        }
@@ -329,7 +364,8 @@
        // 阅卷后才往exam_paper_answer保存考试成绩、以及保存到exam_paper_customer_answer
        // 现在只需要保存到一张临时表
        // 该接口是主动提交,所以状态都设置为完成,以便后续老师阅卷
        saveTempExam(submitData, ExamSubmitTempStatusEnum.FINISH);
        //TODO
        saveTempExam(submitData, ExamSubmitTempStatusEnum.finish);
        return Result.ok();
    }
@@ -340,39 +376,46 @@
     * @return
     */
    @Override
    public Result timingSubmit(ExamSubmitVO submitData) {
        saveTempExam(submitData, ExamSubmitTempStatusEnum.TEMP);
    public Result timingSubmit(StartExamVO submitData) {
        //TODO
        saveTempExam(submitData, ExamSubmitTempStatusEnum.temp);
        return Result.ok();
    }
    /**
     * 保存试卷:如果接口是定时保存那么是临时试卷。如果接口是自主提交那么是完成试卷
     *
     * @param submitData  前端传递的试卷数据
     * @param status  试卷的状态
     * @param submitData 前端传递的试卷数据
     * @param status     试卷的状态
     */
    public void saveTempExam(ExamSubmitVO submitData, ExamSubmitTempStatusEnum status) {
    public void saveTempExam(StartExamVO submitData, ExamSubmitTempStatusEnum status) {
        ExamSubmitTemp one = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
                .eq(ExamSubmitTemp::getExamId, submitData.getExamId())
                .eq(ExamSubmitTemp::getExamId, submitData.getId())
                .eq(ExamSubmitTemp::getUserId, webContext.getCurrentUser().getId())
                .eq(ExamSubmitTemp::getDeleted,0)
                .one();
        if (Objects.nonNull(one)) {
            if (ExamSubmitTempStatusEnum.FINISH.equals(one.getStatus())) {
            long doTimeL = one.getUpdateTime().getTime() - one.getCreateTime().getTime();
            Integer doTime = (int) doTimeL;
            //TODO
            if (ExamSubmitTempStatusEnum.finish.equals(one.getStatus())) {
                return;
            }
            one.setDoTime(submitData.getDoTime());
            one.setExamSubmit(JSON.toJSONString(submitData.getPaperQuestionList()));
            one.setCreateTime(new Date());
            one.setDoTime(doTime);
            one.setExamSubmit(JSON.toJSONString(submitData.getTitleList()));
            one.setUpdateTime(new Date());
            one.setStatus(status);
            examSubmitTempMapper.updateById(one);
        } else {
            ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp();
            examSubmitTemp.setExamId(submitData.getExamId());
            examSubmitTemp.setDoTime(submitData.getDoTime());
            examSubmitTemp.setExamId(submitData.getId());
            examSubmitTemp.setDoTime(0);
            examSubmitTemp.setStatus(status);
            examSubmitTemp.setUserId(webContext.getCurrentUser().getId());
            examSubmitTemp.setExamSubmit(JSON.toJSONString(submitData.getPaperQuestionList()));
            examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.TEMP);
            examSubmitTemp.setExamSubmit(JSON.toJSONString(submitData.getTitleList()));
            //TODO
            examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.temp);
            examSubmitTempMapper.insert(examSubmitTemp);
        }
    }
@@ -393,7 +436,8 @@
        // 参考人数
        Integer joinExamNum = examSubmitTempList.size();
        // 参考但未完成提交人数
        Integer joinButNotFinishedNum = Math.toIntExact(examSubmitTempList.stream().filter(item -> ExamSubmitTempStatusEnum.TEMP.equals(item.getStatus())).count());
        //TODO
        Integer joinButNotFinishedNum = Math.toIntExact(examSubmitTempList.stream().filter(item -> ExamSubmitTempStatusEnum.temp.equals(item.getStatus())).count());
        List<StudentExamInfoVO> studentExamList = classesUserMapper.getClassesUserList(exam.getClassesId());
        // 应考人数
@@ -421,6 +465,7 @@
    @Override
    public Result getMarkPaperInfo(Integer examId, Integer userId) {
        //学生答题表
        ExamSubmitTemp userExam = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
                .eq(ExamSubmitTemp::getExamId, examId)
                .eq(ExamSubmitTemp::getUserId, userId)
@@ -428,14 +473,214 @@
        if (Objects.isNull(userExam)) {
            throw new RuntimeException("该学员考试记录不存在");
        }
        ExamSubmitVO vo = new ExamSubmitVO();
        vo.setExamId(userExam.getExamId());
        vo.setDoTime(userExam.getDoTime());
        vo.setUpdateTime(userExam.getUpdateTime());
        if (StringUtils.hasText(userExam.getExamSubmit())) {
            vo.setPaperQuestionList(JSON.parseArray(userExam.getExamSubmit(), PaperFixQuestionVO.class));
        ExamVO exam = examMapper.getById(examId);
        User student = userMapper.getUserById(userId);
        //封装阅卷基本数据
        ExamPaperMarkVO paperMarkVO = createVO(userExam, exam, student);
        //TODO:补充题目答案、解析
        List<PaperFixQuestionVO> titleItems = paperMarkVO.getTitleItems();
        for (PaperFixQuestionVO titleItem : titleItems) {
            for (DoQuestionVO doQuestionVO : titleItem.getQuestionList()) {
            }
        }
        return Result.ok(vo);
        //阅卷,客观题打分
        Result InnerError = markPaper(paperMarkVO);
        if (InnerError != null) return InnerError;
        return Result.ok(paperMarkVO);
    }
    //提交批改
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result submitMarkPaper(ExamPaperMarkVO examPaperMark) {
        Integer userId = webContext.getCurrentUser().getId();
        //插入exam_paper_answer(成绩表)
        ExamPaperScore examPaperScore = new ExamPaperScore();
        BeanUtils.copyProperties(examPaperMark,examPaperScore);
        examPaperScore.setUserScore(new BigDecimal(examPaperMark.getScore()));
        examPaperScore.setPaperScore(new BigDecimal(examPaperMark.getTotalScore()));
        examPaperScore.setJudgeUser(userId);
        examPaperScore.setCreateUser(examPaperMark.getUserId());
        examPaperScore.setAnswerTime(examPaperMark.getSubmitTime());
        examPaperScore.setPaperContent(JSON.toJSONString(examPaperMark.getTitleItems()));
        long questionCorrect =0;
        long questionCount=0;
        if(!CollectionUtils.isEmpty(examPaperMark.getAnswers())){
            questionCorrect = examPaperMark.getAnswers().stream().filter(ExamPaperMarkAnswerVO::getRight).count();
            questionCount = examPaperMark.getAnswers().size();
        }
        examPaperScore.setQuestionCorrect(Integer.valueOf(questionCorrect+""));
        examPaperScore.setQuestionCount(Integer.valueOf(questionCount+""));
        examPaperScoreMapper.insert(examPaperScore);
        return Result.ok();
    }
    //阅卷
    private Result markPaper(ExamPaperMarkVO paperMarkVO) {
        List<PaperFixQuestionVO> titleItems = paperMarkVO.getTitleItems();
        //初始化题目序号
        Integer num = 1;
        List<ExamPaperMarkAnswerVO> answers = new ArrayList<>();
        for (PaperFixQuestionVO titleItem : titleItems) {
            for (DoQuestionVO doQuestionVO : titleItem.getQuestionList()) {
                //准备题目序号供前端跳转使用
                ExamPaperMarkAnswerVO answerVO = new ExamPaperMarkAnswerVO();
                //获取试卷类型
                Integer questionType = doQuestionVO.getQuestionType();
                /* 如果是简答、计算、分析,不设置right只设置题目序号 */
                if (QuestionTypeEnum.ShortAnswer.getCode().equals(questionType) || QuestionTypeEnum.Calculate.getCode().equals(questionType) || QuestionTypeEnum.Analysis.getCode().equals(questionType)) {
                    answerVO.setItemOrder(num);
                    doQuestionVO.setItemOrder(num);
                }
                /* 如果是单选、语音、判断(判断答案是A、B) */
                else if (QuestionTypeEnum.SingleChoice.getCode().equals(questionType) || QuestionTypeEnum.Audio.getCode().equals(questionType) || QuestionTypeEnum.TrueFalse.getCode().equals(questionType)) {
                    answerVO.setItemOrder(num);
                    doQuestionVO.setItemOrder(num);
                    if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) {
                        return Result.fail(SystemCode.InnerError.getCode(), doQuestionVO.getTitle() + ",此题目缺少答案,请先完善");
                    }
                    trueOrFalse(doQuestionVO, answerVO, doQuestionVO.getQuestionAnswer().equals(doQuestionVO.getAnswer()));
                }
                /* 如果是多选 */
                else if (QuestionTypeEnum.MultipleChoice.getCode().equals(questionType)) {
                    answerVO.setItemOrder(num);
                    doQuestionVO.setItemOrder(num);
                    if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) {
                        return Result.fail(SystemCode.InnerError.getCode(), doQuestionVO.getTitle() + ",此题目缺少答案,请先完善");
                    }
                    //学生答案
                    List<String> answerList = doQuestionVO.getAnswerList();
                    String questionAnswer = doQuestionVO.getQuestionAnswer();
                    //题目答案
                    List<String> questionAnswerList = Arrays.asList(questionAnswer.split(","));
                    //学生答案为空,判断为错
                    if (CollectionUtils.isEmpty(answerList)) {
                        trueOrFalse(doQuestionVO, answerVO, false);
                        continue;
                    }
                    //答案数量,不需要考虑顺序
                    int answerCount = answerList.size();
                    Set<String> set1 = new HashSet<>(answerList);
                    Set<String> set2 = new HashSet<>(questionAnswerList);
                    //答案完全一致,满分
                    if (set1.equals(set2)) {
                        trueOrFalse(doQuestionVO, answerVO, true);
                        continue;
                    }
                    if (paperMarkVO.getDeductType() == null) {
                        return Result.fail(SystemCode.InnerError.getCode(), "试卷没有配置多选得分类型,请联系管理员");
                    }
                    //如果多选得分类型为 答错不得分
                    if (Integer.valueOf(DeductTypeEnum.AllCorrect.getCode()).equals(paperMarkVO.getDeductType())) {
                        trueOrFalse(doQuestionVO, answerVO, false);
                    }
                    //如果多选得分类型为 漏选得固定分值,包含错误选项不得分
                    else if (Integer.valueOf(DeductTypeEnum.PartCorrect.getCode()).equals(paperMarkVO.getDeductType())) {
                        //学生答案移除所有正确答案,如果还有元素说明包含错误选项
                        answerList.removeAll(questionAnswerList);
                        if (!CollectionUtils.isEmpty(answerList)) {
                            trueOrFalse(doQuestionVO, answerVO, false);
                        } else {
                            answerVO.setRight(false);
                            doQuestionVO.setRight(false);
                            //漏选得固定分
                            doQuestionVO.setScore(paperMarkVO.getDeductScore());
                        }
                    }
                    //如果多选得分类型为 每对一题得相应分值,包含错误选项不得分
                    else if (Integer.valueOf(DeductTypeEnum.EachCorrect.getCode()).equals(paperMarkVO.getDeductType())) {
                        //学生答案移除所有正确答案,如果还有元素说明包含错误选项
                        answerList.removeAll(questionAnswerList);
                        if (!CollectionUtils.isEmpty(answerList)) {
                            trueOrFalse(doQuestionVO, answerVO, false);
                        } else {
                            answerVO.setRight(false);
                            doQuestionVO.setRight(false);
                            //漏选得分
                            doQuestionVO.setScore(paperMarkVO.getDeductScore().multiply(new BigDecimal(answerCount)));
                        }
                    }
                }
                /* 如果是填空 */
                else if (QuestionTypeEnum.GapFilling.getCode().equals(questionType)) {
                    answerVO.setItemOrder(num);
                    doQuestionVO.setItemOrder(num);
                    if (StringUtils.isEmpty(doQuestionVO.getQuestionAnswer())) {
                        return Result.fail(SystemCode.InnerError.getCode(), doQuestionVO.getTitle() + ",此题目缺少答案,请先完善");
                    }
                    //学生答案
                    List<String> answerList = doQuestionVO.getAnswerList();
                    String questionAnswer = doQuestionVO.getQuestionAnswer();
                    //题目答案
                    List<String> questionAnswerList = Arrays.asList(questionAnswer.split(","));
                    //学生答案为空,判断为错
                    if (CollectionUtils.isEmpty(answerList)) {
                        trueOrFalse(doQuestionVO, answerVO, false);
                        continue;
                    }
                    //总空的数量
                    int questionAnswerCount = questionAnswerList.size();
                    //答案完全一致,满分
                    if (answerList.equals(questionAnswerList)) {
                        trueOrFalse(doQuestionVO, answerVO, true);
                    } else {
                        answerVO.setRight(false);
                        doQuestionVO.setRight(false);
                        //做对的数量,需要按顺序比较
                        int count = 0;
                        for (int i = 0; i < answerList.size(); i++) {
                            if(answerList.get(i).equals(questionAnswerList.get(i))){
                                count++;
                            }
                        }
                        //这个题的总分
                        BigDecimal questionScore = doQuestionVO.getQuestionScore();
                        //每个空的分数
                        BigDecimal scoreEach = questionScore.divide(new BigDecimal(questionAnswerCount), 1, RoundingMode.DOWN);
                        BigDecimal score = scoreEach.multiply(new BigDecimal(count));
                        doQuestionVO.setScore(score);
                    }
                }
                num++;
                answers.add(answerVO);
            }
        }
        paperMarkVO.setAnswers(answers);
        return null;
    }
    //设置全对或全错
    private void trueOrFalse(DoQuestionVO doQuestionVO, ExamPaperMarkAnswerVO answerVO, Boolean isCorrect) {
        if (isCorrect) {
            //正确
            answerVO.setRight(isCorrect);
            doQuestionVO.setRight(isCorrect);
            doQuestionVO.setScore(doQuestionVO.getQuestionScore());
        } else {
            //错误
            answerVO.setRight(isCorrect);
            doQuestionVO.setRight(isCorrect);
            doQuestionVO.setScore(BigDecimal.ZERO);
        }
    }
    //封装阅卷返回数据
    private ExamPaperMarkVO createVO(ExamSubmitTemp userExam, ExamVO exam, User student) {
        ExamPaperMarkVO paperMarkVO = new ExamPaperMarkVO();
        BeanUtils.copyProperties(userExam, paperMarkVO);
        paperMarkVO.setPaperId(exam.getExamPaperId());
        //TODO: 试卷总分、(多选得分类型、多选得分分数)需要取ExamSubmitTemp
        paperMarkVO.setExamName(exam.getExamName());
        paperMarkVO.setPaperType(exam.getExamPaperType());
        paperMarkVO.setSubmitTime(userExam.getUpdateTime());
        paperMarkVO.setUserName(student.getRealName());
        paperMarkVO.setTitleItems(JSON.parseArray(userExam.getExamSubmit(), PaperFixQuestionVO.class));
        paperMarkVO.setTotalScore("100");
        paperMarkVO.setDeductType(DeductTypeEnum.AllCorrect.getCode());
        paperMarkVO.setDeductScore(BigDecimal.ZERO);
        return paperMarkVO;
    }
    @Override