package com.ycl.jxkg.service.impl;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
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.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.exam.PaperFixQuestionDTO;
|
import com.ycl.jxkg.domain.exam.PaperQuestionSettingDTO;
|
import com.ycl.jxkg.domain.form.AddTimeForm;
|
import com.ycl.jxkg.domain.form.ExamForm;
|
import com.ycl.jxkg.domain.form.ForceSubmitForm;
|
import com.ycl.jxkg.domain.query.ExamQuery;
|
import com.ycl.jxkg.domain.question.QuestionObject;
|
import com.ycl.jxkg.domain.vo.*;
|
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;
|
import com.ycl.jxkg.mapper.*;
|
import com.ycl.jxkg.server.WebsocketServer;
|
import com.ycl.jxkg.service.ExamPaperService;
|
import com.ycl.jxkg.service.ExamService;
|
import com.ycl.jxkg.utils.PageUtil;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.BeanUtils;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.Assert;
|
import org.springframework.util.StringUtils;
|
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* 考试 服务实现类
|
*
|
* @author xp
|
* @since 2024-06-11
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {
|
|
private final ExamMapper examMapper;
|
private final WebContext webContext;
|
private final QuestionMapper questionMapper;
|
private final ExamSubmitTempMapper examSubmitTempMapper;
|
private final ClassesUserMapper classesUserMapper;
|
private final ExamPaperMapper examPaperMapper;
|
private final ExamPaperService examPaperService;
|
private final WebsocketServer websocketServer;
|
|
/**
|
* 添加
|
*
|
* @param form
|
* @return
|
*/
|
@Override
|
public Result add(ExamForm form) {
|
Exam entity = ExamForm.getEntityByForm(form, null);
|
entity.setStatus(ExamStatusEnum.getStatusByTime(form.getStartTime(), form.getEndTime(), null));
|
entity.setTeacherId(webContext.getCurrentUser().getId());
|
baseMapper.insert(entity);
|
return Result.ok("添加成功");
|
}
|
|
/**
|
* 修改
|
*
|
* @param form
|
* @return
|
*/
|
@Override
|
public Result update(ExamForm form) {
|
Exam entity = baseMapper.selectById(form.getId());
|
// 为空抛IllegalArgumentException,做全局异常处理
|
Assert.notNull(entity, "记录不存在");
|
BeanUtils.copyProperties(form, entity);
|
entity.setStatus(ExamStatusEnum.getStatusByTime(form.getStartTime(), form.getEndTime(), null));
|
baseMapper.updateById(entity);
|
return Result.ok("修改成功");
|
}
|
|
/**
|
* 批量删除
|
*
|
* @param ids
|
* @return
|
*/
|
@Override
|
public Result remove(List<String> ids) {
|
baseMapper.deleteBatchIds(ids);
|
return Result.ok("删除成功");
|
}
|
|
/**
|
* id删除
|
*
|
* @param id
|
* @return
|
*/
|
@Override
|
public Result removeById(String id) {
|
baseMapper.deleteById(id);
|
return Result.ok("删除成功");
|
}
|
|
/**
|
* 分页查询
|
*
|
* @param query
|
* @return
|
*/
|
@Override
|
public Result page(ExamQuery query) {
|
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())) {
|
item.setClassName("班级不存在或被删除");
|
item.setClassesId(null);
|
}
|
if (! StringUtils.hasText(item.getExamPaperName())) {
|
item.setExamPaperName("试卷不存在或被删除");
|
item.setExamPaperId(null);
|
}
|
});
|
return Result.ok().data(page.getRecords()).total(page.getTotal());
|
}
|
|
|
@Override
|
public Result studentPage(ExamQuery query) {
|
IPage<ExamVO> page = PageUtil.getPage(query, ExamVO.class);
|
baseMapper.studentPage(page, query, webContext.getCurrentUser().getId());
|
return Result.ok().data(page.getRecords()).total(page.getTotal());
|
}
|
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Result start(Integer id) {
|
Exam exam = baseMapper.selectById(id);
|
if (Objects.isNull(exam)) {
|
throw new RuntimeException("该考试不存在");
|
}
|
if (Objects.isNull(exam.getExamPaperId())) {
|
throw new RuntimeException("考试未绑定试卷");
|
}
|
if (ExamStatusEnum.NOT_START.equals(exam.getStatus())) {
|
throw new RuntimeException("考试还未开始");
|
}
|
if (ExamStatusEnum.FINISHED.equals(exam.getStatus())) {
|
throw new RuntimeException("考试已经结束");
|
}
|
// 查出考试试卷
|
ExamPaper examPaper = examPaperMapper.selectById(exam.getExamPaperId());
|
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())) {
|
if (StringUtils.hasText(examPaper.getContent())) {
|
throw new RuntimeException("试卷题目为空");
|
}
|
// 转换
|
List<PaperFixQuestionVO> data = this.coverTo(examPaper);
|
return Result.ok().data(data);
|
} else if (ExamPaperTypeEnum.Random.getCode().equals(examPaper.getPaperType())) {
|
// 根据随机试卷的配置,随机生成对应题目
|
if (! StringUtils.hasText(examPaper.getContent())) {
|
throw new RuntimeException("试卷配置异常,请联系老师");
|
}
|
List<PaperQuestionSettingDTO> paperSettingList = JSON.parseArray(examPaper.getContent(), PaperQuestionSettingDTO.class);
|
|
// 试卷内容
|
List<PaperFixQuestionVO> examData = new ArrayList<>(8);
|
for (PaperQuestionSettingDTO paperSetting : paperSettingList) {
|
PaperFixQuestionVO paperFixQuestionVO = new PaperFixQuestionVO();
|
paperFixQuestionVO.setTitle(paperSetting.getTitle());
|
paperFixQuestionVO.setQuestionType(paperSetting.getQuestionType());
|
// 拿到课目下某类题型的x道随机题
|
List<Question> questions = questionMapper.getRandomQuestion(examPaper.getSubjectId(), paperSetting.getQuestionType(), paperSetting.getNum());
|
if (paperSetting.getNum() > questions.size()) {
|
throw new RuntimeException("配置的题目数不足以生成试卷");
|
}
|
// 拿到题目后组装为可临时保存的题目结构
|
List<DoQuestionVO> childQuestions = questions.stream().map(item -> {
|
DoQuestionVO doQuestionVO = new DoQuestionVO();
|
doQuestionVO.setTitle(item.getTitle());
|
doQuestionVO.setQuestionType(item.getQuestionType());
|
if (StringUtils.hasText(item.getContent())) {
|
QuestionObject questionObject = JSON.parseObject(item.getContent(), QuestionObject.class);
|
doQuestionVO.setQuestionItemList(questionObject.getQuestionItemObjects());
|
}
|
doQuestionVO.setId(item.getId());
|
doQuestionVO.setOriginalFile(item.getOriginalFile());
|
doQuestionVO.setAudioFile(item.getAudioFile());
|
return doQuestionVO;
|
}).collect(Collectors.toList());
|
paperFixQuestionVO.setQuestionList(childQuestions);
|
examData.add(paperFixQuestionVO);
|
}
|
ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp();
|
examSubmitTemp.setExamId(id);
|
examSubmitTemp.setStatus(ExamSubmitTempStatusEnum.TEMP);
|
examSubmitTemp.setExamSubmit(JSON.toJSONString(examData));
|
examSubmitTemp.setCreateTime(new Date());
|
examSubmitTemp.setUserId(webContext.getCurrentUser().getId());
|
examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.TEMP);
|
examSubmitTempMapper.insert(examSubmitTemp);
|
StartExamVO startExamVO = new StartExamVO();
|
startExamVO.setExamName(exam.getExamName());
|
startExamVO.setId(exam.getId());
|
startExamVO.setTitleList(examData);
|
startExamVO.setSuggestTime(examPaper.getSuggestTime());
|
return Result.ok(startExamVO);
|
}
|
return Result.ok();
|
}
|
|
/**
|
* 将数据库存储的题目,转为可临时保存的题目结构
|
*
|
* @param examPaper 试卷
|
* @return
|
*/
|
private List<PaperFixQuestionVO> coverTo(ExamPaper examPaper) {
|
if (! StringUtils.hasText(examPaper.getContent())) {
|
throw new RuntimeException("试卷未配置题目,请联系老师");
|
}
|
List<PaperFixQuestionDTO> questionWarpList = JSON.parseArray(examPaper.getContent(), PaperFixQuestionDTO.class);
|
return questionWarpList.stream().map(item -> {
|
PaperFixQuestionVO vo = new PaperFixQuestionVO();
|
vo.setTitle(item.getTitle());
|
vo.setQuestionType(item.getQuestionType());
|
List<DoQuestionVO> doQuestionVOS = item.getQuestionList().stream().map(question -> {
|
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());
|
doQuestionVO.setAudioFile(question.getAudioFile());
|
return doQuestionVO;
|
}).collect(Collectors.toList());
|
if (ExamPaperTypeEnum.RandomOrder.getCode().equals(examPaper.getPaperType())) {
|
// 随序试卷打乱顺序
|
Collections.shuffle(doQuestionVOS);
|
}
|
vo.setQuestionList(doQuestionVOS);
|
return vo;
|
}).collect(Collectors.toList());
|
}
|
|
/**
|
* 根据id查找
|
*
|
* @param id
|
* @return
|
*/
|
@Override
|
public Result detail(Integer id) {
|
ExamVO vo = baseMapper.getById(id);
|
Assert.notNull(vo, "记录不存在");
|
return Result.ok().data(vo);
|
}
|
|
/**
|
* 列表
|
*
|
* @return
|
*/
|
@Override
|
public Result all() {
|
List<Exam> entities = baseMapper.selectList(null);
|
List<ExamVO> vos = entities.stream()
|
.map(entity -> ExamVO.getVoByEntity(entity, null))
|
.collect(Collectors.toList());
|
return Result.ok().data(vos);
|
}
|
|
/**
|
* 主动提交试卷
|
*
|
* @param submitData 试卷做题提交数据
|
* @return
|
*/
|
@Override
|
public Result examSubmit(StartExamVO submitData) {
|
// 校验
|
Exam exam = examMapper.selectById(submitData.getId());
|
if (Objects.isNull(exam)) {
|
throw new RuntimeException("该考试不存在");
|
}
|
// // 判断单选、多选、判断题对错
|
// List<Integer> questionIds = new ArrayList<>(24);
|
// submitData.getPaperQuestionList().forEach(item -> {
|
// List<Integer> ids = item.getQuestionList().stream().map(DoQuestionVO::getId).collect(Collectors.toList());
|
// questionIds.addAll(ids);
|
// });
|
// List<Question> questionList = questionMapper.getAnswerInfo(questionIds);
|
// Map<Integer, Question> answerMap = questionList.stream().collect(Collectors.toMap(Question::getId, entity -> entity));
|
// submitData.getQuestionList().stream().forEach(item -> {
|
// Question question = answerMap.get(item.getId());
|
// if (Objects.nonNull(question)
|
// && (QuestionTypeEnum.SingleChoice.getCode().equals(question.getQuestionType())
|
// || QuestionTypeEnum.MultipleChoice.getCode().equals(question.getQuestionType())
|
// || QuestionTypeEnum.TrueFalse.getCode().equals(question.getQuestionType())
|
// )) {
|
// String correct = question.getCorrect();
|
// if (QuestionTypeEnum.MultipleChoice.getCode().equals(question.getQuestionType())) {
|
// // 如果是选择题,那么将答案转为list
|
// List<String> answerList = JSON.parseArray(correct, String.class);
|
// item.setRight(answerList.containsAll(item.getAnswerList()));
|
// } else {
|
// item.setRight(question.getCorrect().equals(item.getAnswer()));
|
// }
|
// }
|
// });
|
// 阅卷后才往exam_paper_answer保存考试成绩、以及保存到exam_paper_customer_answer
|
// 现在只需要保存到一张临时表
|
// 该接口是主动提交,所以状态都设置为完成,以便后续老师阅卷
|
saveTempExam(submitData, ExamSubmitTempStatusEnum.FINISH);
|
return Result.ok();
|
}
|
|
/**
|
* 临时保存试卷
|
*
|
* @param submitData
|
* @return
|
*/
|
@Override
|
public Result timingSubmit(StartExamVO submitData) {
|
saveTempExam(submitData, ExamSubmitTempStatusEnum.TEMP);
|
return Result.ok();
|
}
|
|
/**
|
* 保存试卷:如果接口是定时保存那么是临时试卷。如果接口是自主提交那么是完成试卷
|
*
|
* @param submitData 前端传递的试卷数据
|
* @param status 试卷的状态
|
*/
|
public void saveTempExam(StartExamVO submitData, ExamSubmitTempStatusEnum status) {
|
ExamSubmitTemp one = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
|
.eq(ExamSubmitTemp::getExamId, submitData.getId())
|
.eq(ExamSubmitTemp::getUserId, webContext.getCurrentUser().getId())
|
.one();
|
if (Objects.nonNull(one)) {
|
if (ExamSubmitTempStatusEnum.FINISH.equals(one.getStatus())) {
|
return;
|
}
|
one.setDoTime(submitData.getDoTime());
|
one.setExamSubmit(JSON.toJSONString(submitData.getTitleList()));
|
one.setCreateTime(new Date());
|
one.setStatus(status);
|
examSubmitTempMapper.updateById(one);
|
} else {
|
ExamSubmitTemp examSubmitTemp = new ExamSubmitTemp();
|
examSubmitTemp.setExamId(submitData.getId());
|
examSubmitTemp.setDoTime(submitData.getDoTime());
|
examSubmitTemp.setStatus(status);
|
examSubmitTemp.setUserId(webContext.getCurrentUser().getId());
|
examSubmitTemp.setExamSubmit(JSON.toJSONString(submitData.getTitleList()));
|
examSubmitTemp.setMarkPaperStatus(ExamSubmitTempStatusEnum.TEMP);
|
examSubmitTempMapper.insert(examSubmitTemp);
|
}
|
}
|
|
@Override
|
public Result getMarkResultInfo(Integer id) {
|
Exam exam = baseMapper.selectById(id);
|
if (Objects.isNull(exam)) {
|
throw new RuntimeException("该考试不存在");
|
}
|
ExamPaper examPaper = examPaperMapper.selectById(exam.getExamPaperId());
|
if (Objects.isNull(examPaper)) {
|
throw new RuntimeException("考试试卷不存在");
|
}
|
List<ExamSubmitTemp> examSubmitTempList = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
|
.eq(ExamSubmitTemp::getExamId, id)
|
.list();
|
// 参考人数
|
Integer joinExamNum = examSubmitTempList.size();
|
// 参考但未完成提交人数
|
Integer joinButNotFinishedNum = Math.toIntExact(examSubmitTempList.stream().filter(item -> ExamSubmitTempStatusEnum.TEMP.equals(item.getStatus())).count());
|
|
List<StudentExamInfoVO> studentExamList = classesUserMapper.getClassesUserList(exam.getClassesId());
|
// 应考人数
|
Integer shouldUserNum = studentExamList.size();
|
|
studentExamList.stream().forEach(item -> {
|
if (StringUtils.hasText(item.getExamSubmit())) {
|
item.setQuestionList(JSON.parseArray(item.getExamSubmit(), DoQuestionVO.class));
|
}
|
});
|
|
MarkPaperVO markPaperVO = new MarkPaperVO();
|
markPaperVO.setExamName(exam.getExamName());
|
markPaperVO.setExamId(exam.getId());
|
markPaperVO.setJoinButNotFinishNum(joinButNotFinishedNum);
|
markPaperVO.setShouldJoinNum(shouldUserNum);
|
markPaperVO.setStudentExamInfoVOList(studentExamList);
|
markPaperVO.setMissJoinNum(shouldUserNum - joinExamNum);
|
markPaperVO.setJoinNum(joinExamNum);
|
markPaperVO.setExamPaperName(examPaper.getName());
|
markPaperVO.setSuggestTime(examPaper.getSuggestTime());
|
return Result.ok().data(markPaperVO);
|
}
|
|
|
@Override
|
public Result getMarkPaperInfo(Integer examId, Integer userId) {
|
ExamSubmitTemp userExam = new LambdaQueryChainWrapper<>(examSubmitTempMapper)
|
.eq(ExamSubmitTemp::getExamId, examId)
|
.eq(ExamSubmitTemp::getUserId, userId)
|
.one();
|
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));
|
}
|
return Result.ok(vo);
|
}
|
|
@Override
|
public Result monitorList(ExamQuery query) {
|
IPage<ExamSubmitTempVO> page = PageUtil.getPage(query, ExamSubmitTempVO.class);
|
return Result.ok((examSubmitTempMapper.monitorList(page, query)));
|
}
|
|
@Override
|
public Result addTime(AddTimeForm form) {
|
WebsocketDataVO websocket = new WebsocketDataVO();
|
websocket.setCommend(WebsocketCommendEnum.DELAYED.getCommend());
|
websocket.setData(form);
|
// 发送websocket消息
|
websocketServer.sendOneMessage(form.getUserId(), JSON.toJSONString(form));
|
return Result.ok("操作成功");
|
}
|
|
@Override
|
public Result forceSubmit(ForceSubmitForm form) {
|
WebsocketDataVO websocket = new WebsocketDataVO();
|
websocket.setCommend(WebsocketCommendEnum.FORCE_SUBMIT.getCommend());
|
websocket.setData(form);
|
// 发送websocket消息
|
websocketServer.sendOneMessage(form.getUserId(), JSON.toJSONString(form));
|
return Result.ok("操作成功");
|
}
|
}
|