You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
spring-boot-online-exam/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java

820 lines
44 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/***********************************************************
* @Description : 考试服务接口实现
* @author : 梁山广(Laing Shan Guang)
* @date : 2019-05-28 08:06
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.service.impl;
import cn.hutool.core.util.IdUtil;// Hutool工具类用于生成唯一ID等
import cn.hutool.core.util.StrUtil;// Hutool工具类用于字符串处理
import lsgwr.exam.entity.*;// 导入所有实体类
import lsgwr.exam.enums.QuestionEnum;// 导入枚举类
import lsgwr.exam.service.ExamService;// 导入服务接口
import lsgwr.exam.repository.*; // 导入所有仓库接口
import lsgwr.exam.vo.*;// 导入所有值对象View Object
import org.springframework.beans.BeanUtils;// Spring提供的Bean工具类用于属性复制
import org.springframework.stereotype.Service;// Spring提供的注解用于声明服务类
import javax.transaction.Transactional;// 事务注解,用于声明事务性方法
import java.util.*;// 导入Java集合框架相关类
// 使用@Service注解声明这是一个服务类用于实现业务逻辑
@Service
// 使用@Transactional注解声明这个服务类中的所有方法都运行在事务环境中
@Transactional
public class ExamServiceImpl implements ExamService {
// 注入所有需要的仓库接口
private final ExamRepository examRepository;
private final ExamRecordRepository examRecordRepository;
private final QuestionRepository questionRepository;
private final UserRepository userRepository;
private final QuestionLevelRepository questionLevelRepository;
private final QuestionTypeRepository questionTypeRepository;
private final QuestionCategoryRepository questionCategoryRepository;
private final QuestionOptionRepository questionOptionRepository;
// 通过构造函数注入所有仓库接口
public ExamServiceImpl(QuestionRepository questionRepository, UserRepository userRepository, QuestionLevelRepository questionLevelRepository, QuestionTypeRepository questionTypeRepository, QuestionCategoryRepository questionCategoryRepository, QuestionOptionRepository questionOptionRepository, ExamRepository examRepository, ExamRecordRepository examRecordRepository) {
// 依次赋值给对应的成员变量
this.questionRepository = questionRepository;
this.userRepository = userRepository;
this.questionLevelRepository = questionLevelRepository;
this.questionTypeRepository = questionTypeRepository;
this.questionCategoryRepository = questionCategoryRepository;
this.questionOptionRepository = questionOptionRepository;
this.examRepository = examRepository;
this.examRecordRepository = examRecordRepository;
}
// 实现获取所有问题的接口
@Override
public List<QuestionVo> getQuestionAll() {
// 从问题仓库中获取所有问题实体
List<Question> questionList = questionRepository.findAll();
// 将问题实体列表转换为问题值对象列表
return getQuestionVos(questionList);
}
// 私有方法,用于将问题实体列表转换为问题值对象列表
private List<QuestionVo> getQuestionVos(List<Question> questionList) {
// 初始化值对象列表
List<QuestionVo> questionVoList = new ArrayList<>();
// 遍历问题实体列表
for (Question question : questionList) {
// 将每个问题实体转换为值对象
QuestionVo questionVo = getQuestionVo(question);
// 将值对象添加到列表中
questionVoList.add(questionVo);
}
// 返回值对象列表
return questionVoList;
}
// 私有方法,用于将单个问题实体转换为问题值对象
private QuestionVo getQuestionVo(Question question) {
// 初始化值对象
QuestionVo questionVo = new QuestionVo();
// 复制问题实体中的属性到值对象
BeanUtils.copyProperties(question, questionVo);
// 设置问题的创建者用户名通过用户ID从用户仓库中获取
questionVo.setQuestionCreator(
Objects.requireNonNull(
userRepository.findById(
question.getQuestionCreatorId()
).orElse(null)
).getUserUsername());// 使用orElseThrow抛出异常避免使用requireNonNull和orElse(null)
// 设置问题的难度描述通过难度ID从难度仓库中获取
questionVo.setQuestionLevel(
Objects.requireNonNull(
questionLevelRepository.findById(
question.getQuestionLevelId()
).orElse(null)
).getQuestionLevelDescription());
// 设置问题的类型描述通过类型ID从类型仓库中获取
questionVo.setQuestionType(
Objects.requireNonNull(
questionTypeRepository.findById(
question.getQuestionTypeId()
).orElse(null)
).getQuestionTypeDescription());
// 设置问题的分类名称通过分类ID从分类仓库中获取
questionVo.setQuestionCategory(
Objects.requireNonNull(
questionCategoryRepository.findById(
question.getQuestionCategoryId()
).orElse(null)
).getQuestionCategoryName()
);
// 初始化选项值对象列表
List<QuestionOptionVo> optionVoList = new ArrayList<>();
// 从选项仓库中获取所有选项实体
List<QuestionOption> optionList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionOptionIds().split("-"))
);
// 从选项仓库中获取所有答案选项实体
List<QuestionOption> answerList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionAnswerOptionIds().split("-"))
);
// 遍历选项实体列表,转换为值对象,并设置答案属性
for (QuestionOption option : optionList) {
QuestionOptionVo optionVo = new QuestionOptionVo();
BeanUtils.copyProperties(option, optionVo);
// 判断当前选项是否是答案
for (QuestionOption answer : answerList) {
if (option.getQuestionOptionId().equals(answer.getQuestionOptionId())) {
optionVo.setAnswer(true);
break;// 找到答案后无需继续遍历
}
}
// 将选项值对象添加到列表中
optionVoList.add(optionVo);
}
// 设置问题的所有选项值对象列表
questionVo.setQuestionOptionVoList(optionVoList);
// 返回问题值对象
return questionVo;
}
// 实现更新问题的接口
@Override
public QuestionVo updateQuestion(QuestionVo questionVo) {
// 初始化答案ID字符串构建器
StringBuilder questionAnswerOptionIds = new StringBuilder();
// 初始化选项实体列表
List<QuestionOption> questionOptionList = new ArrayList<>();
// 获取问题值对象中的选项值对象列表
List<QuestionOptionVo> questionOptionVoList = questionVo.getQuestionOptionVoList();
// 获取选项值对象列表的大小
int size = questionOptionVoList.size();
// 遍历选项值对象列表
for (int i = 0; i < questionOptionVoList.size(); i++) {
// 获取当前选项值对象
QuestionOptionVo questionOptionVo = questionOptionVoList.get(i);
// 初始化选项实体
QuestionOption questionOption = new QuestionOption();
// 复制选项值对象中的属性到选项实体
BeanUtils.copyProperties(questionOptionVo, questionOption);
// 将选项实体添加到列表中
questionOptionList.add(questionOption);
// 判断当前选项是否是答案
if (questionOptionVo.getAnswer()) {
if (i != size - 1) {
// 如果不是最后一个答案则添加ID并用"-"连接
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()).append("-");
} else {
// 如果是最后一个答案则直接添加ID
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId());
}
}
}
// 1.更新问题
Question question = questionRepository.findById(questionVo.getQuestionId()).orElse(null);
// 确保找到的问题不为null否则将抛出异常
assert question != null;
// 将传入的问题对象questionVo的属性复制到持久化对象question
BeanUtils.copyProperties(questionVo, question);
// 更新问题的答案选项ID字符串假设是以某种格式串联的ID字符串
question.setQuestionAnswerOptionIds(questionAnswerOptionIds.toString());
// 保存更新后的问题到数据库
questionRepository.save(question);
// 2.更新所有的option
questionOptionRepository.saveAll(questionOptionList);
// 返回更新后的问题对象转换为VO对象方便前端局部刷新
return getQuestionVo(question);
}
// 重写问题创建方法
@Override
public void questionCreate(QuestionCreateVo questionCreateVo) {
// 创建一个新的Question对象
Question question = new Question();
// 使用BeanUtils工具类将questionCreateVo中的属性复制到question对象中
BeanUtils.copyProperties(questionCreateVo, question);
// 初始化选项列表
List<QuestionOption> questionOptionList = new ArrayList<>();
// 获取问题创建对象中的选项列表
List<QuestionOptionCreateVo> questionOptionCreateVoList = questionCreateVo.getQuestionOptionCreateVoList();
// 遍历选项创建对象列表
for (QuestionOptionCreateVo questionOptionCreateVo : questionOptionCreateVoList) {
// 为每个选项创建一个新的QuestionOption对象
QuestionOption questionOption = new QuestionOption();
// 设置选项的内容
questionOption.setQuestionOptionContent(questionOptionCreateVo.getQuestionOptionContent());
// 使用Hutool的IdUtil生成一个简单的UUID作为选项的ID
questionOption.setQuestionOptionId(IdUtil.simpleUUID());
// 将选项添加到列表中
questionOptionList.add(questionOption);
}
// 将所有选项保存到数据库
questionOptionRepository.saveAll(questionOptionList);
// 初始化选项ID和答案选项ID的字符串
String questionOptionIds = "";
String questionAnswerOptionIds = "";
// 遍历选项创建对象列表,获取保存后的选项对象
for (int i = 0; i < questionOptionCreateVoList.size(); i++) {
// 获取指定选项
QuestionOptionCreateVo questionOptionCreateVo = questionOptionCreateVoList.get(i);
// 获取保存后的指定对象
QuestionOption questionOption = questionOptionList.get(i);
// 拼接选项ID
questionOptionIds += questionOption.getQuestionOptionId() + "-";
if (questionOptionCreateVo.getAnswer()) {
// 如果是答案的话
questionAnswerOptionIds += questionOption.getQuestionOptionId() + "-";
}
}
// 去除选项ID和答案选项ID字符串末尾的"-"
questionAnswerOptionIds = replaceLastSeparator(questionAnswerOptionIds);
questionOptionIds = replaceLastSeparator(questionOptionIds);
// 设置问题对象的选项ID和答案选项ID
question.setQuestionOptionIds(questionOptionIds);
// 设置答案选项id组成的字符串
question.setQuestionAnswerOptionIds(questionAnswerOptionIds);
// 为问题生成一个UUID作为ID
question.setQuestionId(IdUtil.simpleUUID());
// 设置问题的创建时间和更新时间为当前时间
question.setCreateTime(new Date());
question.setUpdateTime(new Date());
// 将问题保存到数据库
questionRepository.save(question);
}
// 重写获取选择项的方法
@Override
public QuestionSelectionVo getSelections() {
// 创建一个新的QuestionSelectionVo对象
QuestionSelectionVo questionSelectionVo = new QuestionSelectionVo();
// 设置问题分类列表、问题等级列表和问题类型列表
questionSelectionVo.setQuestionCategoryList(questionCategoryRepository.findAll());
questionSelectionVo.setQuestionLevelList(questionLevelRepository.findAll());
questionSelectionVo.setQuestionTypeList(questionTypeRepository.findAll());
return questionSelectionVo;
}
// 去除字符串末尾的"-"的方法
public static String trimMiddleLine(String str) {
// 如果字符串的最后一个字符是"-",则去除它
if (str.charAt(str.length() - 1) == '-') {
str = str.substring(0, str.length() - 1);
}
return str;
}
// 注意原代码中有一个方法名为replaceLastSeparator但提供的实现是trimMiddleLine这里假设它们功能相同
@Override
// 重写获取问题详情的方法
public QuestionDetailVo getQuestionDetail(String id) {
// 根据ID从数据库获取问题对象
Question question = questionRepository.findById(id).orElse(null);
// 创建一个新的QuestionDetailVo对象
QuestionDetailVo questionDetailVo = new QuestionDetailVo();
// 设置问题详情对象的ID、名称和描述
questionDetailVo.setId(id);
questionDetailVo.setName(question.getQuestionName());
questionDetailVo.setDescription(question.getQuestionDescription());
// 获取问题类型描述
questionDetailVo.setType(
Objects.requireNonNull(
questionTypeRepository.findById(
question.getQuestionTypeId()
).orElse(null)
).getQuestionTypeDescription()
);
// 获取问题的选项ID字符串去除末尾的"-",然后分割成数组
String optionIdsStr = trimMiddleLine(question.getQuestionOptionIds());
String[] optionIds = optionIdsStr.split("-");
// 根据选项ID数组从数据库获取选项列表
List<QuestionOption> optionList = questionOptionRepository.findAllById(Arrays.asList(optionIds));
// 设置问题详情对象的选项列表
questionDetailVo.setOptions(optionList);
return questionDetailVo;
}
// 重写获取所有考试的方法
@Override
public List<ExamVo> getExamAll() {
// 从数据库获取所有考试对象
List<Exam> examList = examRepository.findAll();
// 将考试对象列表转换为ExamVo列表
return getExamVos(examList);
}
// 定义一个私有方法接收一个Exam对象的列表作为参数返回一个ExamVo对象的列表
private List<ExamVo> getExamVos(List<Exam> examList) {
// 初始化一个空的ExamVo列表用于存放转换后的对象
List<ExamVo> examVoList = new ArrayList<>();
// 遍历传入的Exam列表
for (Exam exam : examList) {
// 为每个Exam对象创建一个对应的ExamVo对象
ExamVo examVo = new ExamVo();
// 使用BeanUtils工具类将Exam对象的属性复制到ExamVo对象中对于名称和类型相同的属性
BeanUtils.copyProperties(exam, examVo);
// 通过userRepository查找考试创建者的信息并设置到ExamVo对象的相应属性上
// 这里使用了Optional的requireNonNull方法确保找到的User对象不为null
examVo.setExamCreator(
Objects.requireNonNull(
userRepository.findById(
exam.getExamCreatorId()
).orElse(null)
).getUserUsername()
);
// 初始化单选题的列表并设置到ExamVo对象的相应属性上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
// 根据考试中的单选题ID字符串分割成ID列表并通过questionRepository查找对应的Question对象列表
List<Question> radioQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsRadio().split("-"))
);
// 遍历单选题列表为每个Question对象创建一个对应的ExamQuestionSelectVo对象并设置选中状态为true
for (Question question : radioQuestionList) {
ExamQuestionSelectVo radioQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, radioQuestionVo);
radioQuestionVo.setChecked(true);// 假设考试中的问题在初始化时都被视为已选中
radioQuestionVoList.add(radioQuestionVo);
}
examVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 创建一个用于存储考试选择题Vo对象的列表
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
// 从数据库中获取所有需要检查(可能是单选或多选,但此处标记为检查题)的问题列表
// 通过分割考试对象中的考试问题ID字符串使用"-"作为分隔符并将结果转换为List<Long>
// 然后调用questionRepository的findAllById方法获取Question对象列表
List<Question> checkQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsCheck().split("-"))
);
// 遍历获取到的问题列表
for (Question question : checkQuestionList) {
// 为每个问题创建一个新的ExamQuestionSelectVo对象
ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo();
// 使用BeanUtils工具类将Question对象的属性复制到新的ExamQuestionSelectVo对象中
BeanUtils.copyProperties(question, checkQuestionVo);
// 设置选中状态为true表示这个问题在考试中是已经被选中的
checkQuestionVo.setChecked(true); // 考试中问题肯定被选中的
// 将设置好的Vo对象添加到列表中
checkQuestionVoList.add(checkQuestionVo);
}
examVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 从数据库中获取所有需要判断的问题列表
// 通过分割考试对象中的考试问题ID字符串使用"-"作为分隔符并将结果转换为List<Long>
// 然后调用questionRepository的findAllById方法获取Question对象列表
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
List<Question> judgeQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsJudge().split("-"))
);
// 遍历获取到的问题列表
for (Question question : judgeQuestionList) {
// 为每个问题创建一个新的ExamQuestionSelectVo对象
ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo();
// 使用BeanUtils工具类将Question对象的属性复制到新的ExamQuestionSelectVo对象中
BeanUtils.copyProperties(question, judgeQuestionVo);
// 设置选中状态为true
judgeQuestionVo.setChecked(true);
// 将设置好的Vo对象添加到列表中
judgeQuestionVoList.add(judgeQuestionVo);
}
examVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList);
// 将处理好的ExamVo对象添加到列表中
examVoList.add(examVo);
}
// 返回处理好的ExamVo列表
return examVoList;
}
// 重写父类或接口中的方法,用于获取考试问题类型的详细信息
@Override
public ExamQuestionTypeVo getExamQuestionType() {
// 初始化一个ExamQuestionTypeVo对象用于存储考试题型信息
ExamQuestionTypeVo examQuestionTypeVo = new ExamQuestionTypeVo();
// 获取所有的单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
// 从数据库中获取所有单选题通过题型ID筛选这里的QuestionEnum.RADIO.getId()返回单选题的ID
List<Question> radioQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.RADIO.getId());
// 遍历所有单选题
for (Question question : radioQuestionList) {
// 为每个单选题创建一个ExamQuestionSelectVo对象
ExamQuestionSelectVo radioQuestionVo = new ExamQuestionSelectVo();
// 将Question对象的属性复制到radioQuestionVo对象中
BeanUtils.copyProperties(question, radioQuestionVo);
// 将复制后的对象添加到单选题的列表中
radioQuestionVoList.add(radioQuestionVo);
}
// 将单选题列表设置到ExamQuestionTypeVo对象的相应属性中
examQuestionTypeVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 初始化多选题的列表
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
// 从数据库中获取所有多选题通过题型ID筛选这里的QuestionEnum.CHECK.getId()返回多选题的ID
List<Question> checkQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.CHECK.getId());
// 遍历所有多选题
for (Question question : checkQuestionList) {
// 为每个多选题创建一个ExamQuestionSelectVo对象
ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo();
// 将Question对象的属性复制到checkQuestionVo对象中
BeanUtils.copyProperties(question, checkQuestionVo);
// 将复制后的对象添加到多选题的列表中
checkQuestionVoList.add(checkQuestionVo);
}
// 将多选题列表设置到ExamQuestionTypeVo对象的相应属性中
examQuestionTypeVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 初始化判断题的列表
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
// 从数据库中获取所有判断题通过题型ID筛选这里的QuestionEnum.JUDGE.getId()返回判断题的ID
List<Question> judgeQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.JUDGE.getId());
// 遍历所有判断题
for (Question question : judgeQuestionList) {
// 为每个判断题创建一个ExamQuestionSelectVo对象
ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo();
// 将Question对象的属性复制到judgeQuestionVo对象中
BeanUtils.copyProperties(question, judgeQuestionVo);
// 将复制后的对象添加到判断题的列表中
judgeQuestionVoList.add(judgeQuestionVo);
}
// 将判断题列表设置到ExamQuestionTypeVo对象的相应属性中
examQuestionTypeVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList);
// 返回包含所有题型信息的ExamQuestionTypeVo对象
return examQuestionTypeVo;
}
@Override
public Exam create(ExamCreateVo examCreateVo, String userId) {
// 创建一个新的考试对象
Exam exam = new Exam();
// 将前端传来的考试创建对象ExamCreateVo的属性复制到新创建的考试对象中
BeanUtils.copyProperties(examCreateVo, exam);
// 为考试生成一个唯一的ID
exam.setExamId(IdUtil.simpleUUID());
// 设置考试的创建者ID
exam.setExamCreatorId(userId);
// 设置考试的创建时间和更新时间(这里都设置为当前时间)
exam.setCreateTime(new Date());
exam.setUpdateTime(new Date());
// Todo:这两个日志后面是要在前端传入的,这里暂时定为当前日期
// Todo: 考试的开始和结束日期应该是前端传入的,这里暂时使用当前日期作为占位符
exam.setExamStartDate(new Date());
exam.setExamEndDate(new Date());
// 初始化字符串用于拼接单选、多选和判断题的ID
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
// 从前端传来的对象中获取单选、多选和判断题的列表
List<ExamQuestionSelectVo> radios = examCreateVo.getRadios();
List<ExamQuestionSelectVo> checks = examCreateVo.getChecks();
List<ExamQuestionSelectVo> judges = examCreateVo.getJudges();
// 初始化计数器,用于记录每种类型题目的数量
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历单选题的列表拼接被选中的题目ID并计数
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
// 替换最后一个分隔符,避免字符串末尾出现多余的分隔符
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历多选题的列表拼接被选中的题目ID并计数
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
// 替换最后一个分隔符
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历判断题的列表拼接被选中的题目ID并计数
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 替换最后一个分隔符
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
// 将所有被选中的题目ID拼接成一个字符串并设置到考试对象中
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 分别设置单选、多选和判断题的ID串到考试对象中
exam.setExamQuestionIdsRadio(radioIdsStr);
exam.setExamQuestionIdsCheck(checkIdsStr);
exam.setExamQuestionIdsJudge(judgeIdsStr);
// 计算考试的总分数
// 总分数 = 单选题数量 * 单选题每题分数 + 多选题数量 * 多选题每题分数 + 判断题数量 * 判断题每题分数
int examScore = radioCnt * exam.getExamScoreRadio() + checkCnt * exam.getExamScoreCheck() + judgeCnt * exam.getExamScoreJudge();
exam.setExamScore(examScore);
// 将考试对象保存到数据库中
examRepository.save(exam);
// 返回创建好的考试对象
return exam;
}
// 重写update方法用于更新考试信息
@Override
public Exam update(ExamVo examVo, String userId) {
// 创建一个新的Exam对象
Exam exam = new Exam();
// 将examVo对象的属性复制到exam对象中
BeanUtils.copyProperties(examVo, exam);
// 设置考试的更新人为当前的用户ID
exam.setExamCreatorId(userId);
// 记录考试的更新日期为当前时间
exam.setUpdateTime(new Date());
// 初始化字符串变量用于存储不同类型的题目ID
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
// 获取单选、多选和判断题的列表
List<ExamQuestionSelectVo> radios = examVo.getExamQuestionSelectVoRadioList();
List<ExamQuestionSelectVo> checks = examVo.getExamQuestionSelectVoCheckList();
List<ExamQuestionSelectVo> judges = examVo.getExamQuestionSelectVoJudgeList();
// 初始化计数器,用于统计每种类型题目的数量
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历单选题目列表拼接被选中的题目ID
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
// 移除最后一个多余的"-"
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历多选题目列表拼接被选中的题目ID
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
// 移除最后一个多余的"-"
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历判断题目列表拼接被选中的题目ID
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 移除最后一个多余的"-"
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
// 设置考试的题目ID字符串包括单选、多选和判断题
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 分别设置各类题目的ID字符串
exam.setExamQuestionIdsRadio(radioIdsStr);
exam.setExamQuestionIdsCheck(checkIdsStr);
exam.setExamQuestionIdsJudge(judgeIdsStr);
// 计算考试的总分数
int examScore = radioCnt * exam.getExamScoreRadio() + checkCnt * exam.getExamScoreCheck() + judgeCnt * exam.getExamScoreJudge();
exam.setExamScore(examScore);
// 保存更新后的考试信息
examRepository.save(exam);
// 返回更新后的考试信息
return exam;
}
// 重写getExamCardList方法用于获取所有考试的信息列表
@Override
public List<ExamCardVo> getExamCardList() {
// 从数据库中获取所有的考试信息
List<Exam> examList = examRepository.findAll();
// 创建一个新的列表,用于存储转换后的考试卡片信息
List<ExamCardVo> examCardVoList = new ArrayList<>();
// 遍历考试信息列表,将每个考试信息转换为考试卡片信息
for (Exam exam : examList) {
ExamCardVo examCardVo = new ExamCardVo();
BeanUtils.copyProperties(exam, examCardVo);
// 将转换后的考试卡片信息添加到列表中
examCardVoList.add(examCardVo);
}
// 返回考试卡片信息列表
return examCardVoList;
}
// 重写getExamDetail方法用于获取指定考试的详细信息
@Override
public ExamDetailVo getExamDetail(String id) {
// 根据ID从数据库中获取考试信息
Exam exam = examRepository.findById(id).orElse(null);
// 创建一个新的考试详细信息对象
ExamDetailVo examDetailVo = new ExamDetailVo();
// 设置考试信息
examDetailVo.setExam(exam);
// 确保考试信息不为空这里使用assert进行断言但通常建议使用更优雅的异常处理
assert exam != null;
// 将考试的各类题目ID字符串分割为数组并设置到考试详细信息对象中
examDetailVo.setRadioIds(exam.getExamQuestionIdsRadio().split("-"));
examDetailVo.setCheckIds(exam.getExamQuestionIdsCheck().split("-"));
examDetailVo.setJudgeIds(exam.getExamQuestionIdsJudge().split("-"));
// 返回考试详细信息对象
return examDetailVo;
}
// 重写judge方法用于判断用户考试的得分情况
@Override
public ExamRecord judge(String userId, String examId, HashMap<String, List<String>> answersMap) {
// 开始考试判分~~~
// 1. 获取考试详情对象和考试对象的选项数组
ExamDetailVo examDetailVo = getExamDetail(examId);// 获取考试详情
Exam exam = examDetailVo.getExam();// 获取考试对象
// 2.然后获取该考试下所有题目信息
List<String> questionIds = new ArrayList<>();// 存储题目ID的列表
// 2.1 获取各种题型的题目ID列表
List<String> radioIdList = Arrays.asList(examDetailVo.getRadioIds());// 单选题ID列表
List<String> checkIdList = Arrays.asList(examDetailVo.getCheckIds());// 多选题ID列表
List<String> judgeIdList = Arrays.asList(examDetailVo.getJudgeIds());// 判断题ID列表
questionIds.addAll(radioIdList);// 将单选题ID添加到总列表
questionIds.addAll(checkIdList);// 将多选题ID添加到总列表
questionIds.addAll(judgeIdList);// 将判断题ID添加到总列表
// 2.2 获取每种题型的分数
int radioScore = exam.getExamScoreRadio();// 单选题每题分数
int checkScore = exam.getExamScoreCheck();// 多选题每题分数
int judgeScore = exam.getExamScoreJudge();// 判断题每题分数
// 2.3 根据题目ID获取所有题目对象便于后续操作
List<Question> questionList = questionRepository.findAllById(questionIds);// 查询所有题目
Map<String, Question> questionMap = new HashMap<>();// 使用Map存储题目ID和题目对象的映射
for (Question question : questionList) {
questionMap.put(question.getQuestionId(), question);// 将题目添加到Map中
}
// 3. 根据正确答案,用户作答信息进行判分
Set<String> questionIdsAnswer = answersMap.keySet();// 获取用户作答的题目ID集合
// 存储每个题目的得分情况
Map<String, Integer> judgeMap = new HashMap<>();
// 用于构建用户作答选项的字符串,用于查看考试详情
StringBuilder answerOptionIdsSb = new StringBuilder();
// 用户考试的总分
int totalScore = 0;
// 遍历用户作答的题目ID
for (String questionId : questionIdsAnswer) {
// 获取题目对象
Question question = questionMap.get(questionId);
// 获取题目正确答案的选项ID并处理格式
String questionAnswerOptionIds = replaceLastSeparator(question.getQuestionAnswerOptionIds());
List<String> questionAnswerOptionIdList = Arrays.asList(questionAnswerOptionIds.split("-"));
Collections.sort(questionAnswerOptionIdList);// 对选项进行排序
String answerStr = listConcat(questionAnswerOptionIdList);// 将选项列表转换为字符串
// 获取用户作答的选项ID列表并处理格式
List<String> questionUserOptionIdList = answersMap.get(questionId);
Collections.sort(questionUserOptionIdList);// 对选项进行排序
String userStr = listConcat(questionUserOptionIdList);// 将选项列表转换为字符串
// 判断用户作答是否正确
if (answerStr.equals(userStr)) {
// 作答正确,根据题型设置分数
int score = 0;
if (radioIdList.contains(questionId)) {
score = radioScore;// 单选题分数
}
if (checkIdList.contains(questionId)) {
score = checkScore;// 多选题分数
}
if (judgeIdList.contains(questionId)) {
score = judgeScore;// 判断题分数
}
// 累加总分
totalScore += score;
// 记录答案正确及用户作答选项
answerOptionIdsSb.append(questionId + "@True_" + userStr + "$");
judgeMap.put(questionId, score);// 记录题目得分
} else {
// 作答错误记0分
answerOptionIdsSb.append(questionId + "@False_" + userStr + "$");
judgeMap.put(questionId, 0);// 记录题目得分为0
}
}
// 4. 计算得分,记录考试结果,并保存到数据库中
ExamRecord examRecord = new ExamRecord();// 创建考试记录对象
examRecord.setExamRecordId(IdUtil.simpleUUID());// 设置考试记录ID
examRecord.setExamId(examId);// 设置考试ID
// 设置用户作答选项字符串,注意处理格式
examRecord.setAnswerOptionIds(replaceLastSeparator(answerOptionIdsSb.toString()));
examRecord.setExamJoinerId(userId);// 设置考生ID
examRecord.setExamJoinDate(new Date());// 设置考试日期
examRecord.setExamJoinScore(totalScore);// 设置考试总分
examRecordRepository.save(examRecord);// 保存考试记录到数据库
return examRecord;// 返回考试记录对象
}
// 重写方法,用于获取指定用户的考试记录列表
@Override
public List<ExamRecordVo> getExamRecordList(String userId) {
// 通过用户ID从数据库中查询该用户参与的考试记录按考试加入日期降序排列
List<ExamRecord> examRecordList = examRecordRepository.findByExamJoinerIdOrderByExamJoinDateDesc(userId);
// 创建一个用于存放转换后的考试记录视图对象的列表
List<ExamRecordVo> examRecordVoList = new ArrayList<>();
// 遍历查询到的考试记录
for (ExamRecord examRecord : examRecordList) {
ExamRecordVo examRecordVo = new ExamRecordVo();// 创建一个新的考试记录视图对象
// 根据考试记录中的考试ID查询考试详情
Exam exam = examRepository.findById(examRecord.getExamId()).orElse(null);
// 设置考试详情到视图对象中
examRecordVo.setExam(exam);
// 根据用户ID查询用户详情
User user = userRepository.findById(userId).orElse(null);
// 设置用户详情到视图对象中
examRecordVo.setUser(user);
// 设置考试记录到视图对象中
examRecordVo.setExamRecord(examRecord);
// 将视图对象添加到列表中
examRecordVoList.add(examRecordVo);
}
return examRecordVoList;
}
// 重写方法,用于获取特定考试记录的详细信息
@Override
public RecordDetailVo getRecordDetail(String recordId) {
// 通过记录ID从数据库中查询考试记录
ExamRecord record = examRecordRepository.findById(recordId).orElse(null);
// 创建一个新的记录详情视图对象
RecordDetailVo recordDetailVo = new RecordDetailVo();
// 设置考试记录到视图对象中
recordDetailVo.setExamRecord(record);
// 初始化用于存储用户答案和结果的哈希映射
HashMap<String, List<String>> answersMap = new HashMap<>();
HashMap<String, String> resultsMap = new HashMap<>();
// 确保查询到的考试记录不为空
assert record != null;
// 获取用户答案字符串,以$分隔每个题目的答案
String answersStr = record.getAnswerOptionIds();
// 按$分割字符串,得到每个题目的答案
String[] questionArr = answersStr.split("[$]");
// 遍历每个题目的答案
for (String questionStr : questionArr) {
System.out.println(questionStr);
// 题目字符串格式为:题目标题@结果_选项1-选项2-...,分割得到题目和选项
String[] questionTitleResultAndOption = questionStr.split("_");
String[] questionTitleAndResult = questionTitleResultAndOption[0].split("@");
String[] questionOptions = questionTitleResultAndOption[1].split("-");
// 将题目和答案选项存入answersMap
answersMap.put(questionTitleAndResult[0], Arrays.asList(questionOptions));
// 将题目和结果True/False存入resultsMap
resultsMap.put(questionTitleAndResult[0], questionTitleAndResult[1]);
}
// 设置答案和结果映射到视图对象中
recordDetailVo.setAnswersMap(answersMap);
recordDetailVo.setResultsMap(resultsMap);
// 获取考试详情用于获取所有题目的ID
ExamDetailVo examDetailVo = getExamDetail(record.getExamId());
// 初始化一个列表用于存放所有题目的ID
List<String> questionIdList = new ArrayList<>();
// 将单选、多选、判断题的ID添加到列表中
questionIdList.addAll(Arrays.asList(examDetailVo.getRadioIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getCheckIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getJudgeIds()));
// 根据题目ID列表从数据库中查询所有题目
List<Question> questionList = questionRepository.findAllById(questionIdList);
// 初始化一个哈希映射用于存储正确答案
HashMap<String, List<String>> answersRightMap = new HashMap<>();
// 遍历所有题目,获取每个题目的正确答案
for (Question question : questionList) {
// 去除答案字符串末尾可能出现的特殊字符(如多余的"-"
String questionAnswerOptionIdsStr = replaceLastSeparator(question.getQuestionAnswerOptionIds());
// 分割字符串得到正确答案
String[] questionAnswerOptionIds = questionAnswerOptionIdsStr.split("-");
// 将题目ID和正确答案存入answersRightMap
answersRightMap.put(question.getQuestionId(), Arrays.asList(questionAnswerOptionIds));
}
// 设置正确答案映射到视图对象中
recordDetailVo.setAnswersRightMap(answersRightMap);
// 返回记录详情视图对象
return recordDetailVo;
}
/**
* 替换掉字符串的最后一个分隔符(如果它是-、_或$)。
*
* @param str 要处理的原始字符串。
* @return 替换掉最后一个分隔符后的字符串。
*/
private String replaceLastSeparator(String str) {
// 获取字符串的最后一个字符。
String lastChar = str.substring(str.length() - 1);
// 检查最后一个字符是否是分隔符(-、_或$)。
if ("-".equals(lastChar) || "_".equals(lastChar) || "$".equals(lastChar)) {
// 如果最后一个字符是分隔符则使用StrUtil.sub方法移除它。
str = StrUtil.sub(str, 0, str.length() - 1);
}
// 返回处理后的字符串。
return str;
}
/**
* 将字符串列表中的元素用-连接起来,并确保最终字符串不以-结尾。
*
* @param strList 包含要连接的字符串的列表。
* @return 拼接好的字符串,确保已经去掉了末尾的-。
*/
private String listConcat(List<String> strList) {
// 创建一个StringBuilder对象用于高效地构建最终的字符串
StringBuilder sb = new StringBuilder();
// 遍历字符串列表。
for (String str : strList) {
// 将当前字符串添加到StringBuilder中。
sb.append(str);
// 在当前字符串后添加一个-作为分隔符。
sb.append("-");
}
// 调用replaceLastSeparator方法移除StringBuilder中拼接字符串的最后一个-。
return replaceLastSeparator(sb.toString());
}
}