diff --git a/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java b/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java index e58d825..778b076 100644 --- a/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java +++ b/backend/src/main/java/lsgwr/exam/service/impl/ExamServiceImpl.java @@ -6,23 +6,24 @@ ***********************************************************/ package lsgwr.exam.service.impl; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import lsgwr.exam.entity.*; -import lsgwr.exam.enums.QuestionEnum; -import lsgwr.exam.service.ExamService; -import lsgwr.exam.repository.*; -import lsgwr.exam.vo.*; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Service; - -import javax.transaction.Transactional; -import java.util.*; - +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; @@ -38,8 +39,9 @@ public class ExamServiceImpl implements ExamService { 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; @@ -49,37 +51,43 @@ public class ExamServiceImpl implements ExamService { this.examRepository = examRepository; this.examRecordRepository = examRecordRepository; } - + // 实现获取所有问题的接口 @Override public List getQuestionAll() { + // 从问题仓库中获取所有问题实体 List questionList = questionRepository.findAll(); + // 将问题实体列表转换为问题值对象列表 return getQuestionVos(questionList); } - + // 私有方法,用于将问题实体列表转换为问题值对象列表 private List getQuestionVos(List questionList) { - // 需要自定义的question列表 + // 初始化值对象列表 List 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()); + ).getUserUsername());// 使用orElseThrow抛出异常,避免使用requireNonNull和orElse(null) - // 设置问题的难度 + // 设置问题的难度描述(通过难度ID从难度仓库中获取) questionVo.setQuestionLevel( Objects.requireNonNull( questionLevelRepository.findById( @@ -87,7 +95,7 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionLevelDescription()); - // 设置题目的类别,比如单选、多选、判断等 + // 设置问题的类型描述(通过类型ID从类型仓库中获取) questionVo.setQuestionType( Objects.requireNonNull( questionTypeRepository.findById( @@ -95,7 +103,7 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionTypeDescription()); - // 设置题目分类,比如数学、语文、英语、生活、人文等 + // 设置问题的分类名称(通过分类ID从分类仓库中获取) questionVo.setQuestionCategory( Objects.requireNonNull( questionCategoryRepository.findById( @@ -104,54 +112,67 @@ public class ExamServiceImpl implements ExamService { ).getQuestionCategoryName() ); - // 选项的自定义Vo列表 + // 初始化选项值对象列表 List optionVoList = new ArrayList<>(); - // 获得所有的选项列表 + // 从选项仓库中获取所有选项实体 List optionList = questionOptionRepository.findAllById( Arrays.asList(question.getQuestionOptionIds().split("-")) ); - - // 获取所有的答案列表optionList中每个option的isAnswer选项 + // 从选项仓库中获取所有答案选项实体 List answerList = questionOptionRepository.findAllById( Arrays.asList(question.getQuestionAnswerOptionIds().split("-")) ); - // 根据选项和答案的id相同设置optionVo的isAnswer属性 + // 遍历选项实体列表,转换为值对象,并设置答案属性 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) { - // 1.把需要的属性都设置好 + // 初始化答案ID字符串构建器 StringBuilder questionAnswerOptionIds = new StringBuilder(); + // 初始化选项实体列表 List questionOptionList = new ArrayList<>(); + // 获取问题值对象中的选项值对象列表 List 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加上去,记得用-连到一起 + // 如果不是最后一个答案,则添加ID并用"-"连接 questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()).append("-"); } else { - // 最后一个不需要用-连接 + // 如果是最后一个答案,则直接添加ID questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()); } } @@ -159,70 +180,80 @@ public class ExamServiceImpl implements ExamService { // 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); - // 设置下questionOptionIds和questionAnswerOptionIds,需要自己用Hutool生成下 + // 初始化选项列表 List questionOptionList = new ArrayList<>(); + // 获取问题创建对象中的选项列表 List questionOptionCreateVoList = questionCreateVo.getQuestionOptionCreateVoList(); + // 遍历选项创建对象列表 for (QuestionOptionCreateVo questionOptionCreateVo : questionOptionCreateVoList) { + // 为每个选项创建一个新的QuestionOption对象 QuestionOption questionOption = new QuestionOption(); - // 设置选项的的内容 + // 设置选项的内容 questionOption.setQuestionOptionContent(questionOptionCreateVo.getQuestionOptionContent()); - // 设置选项的id + // 使用Hutool的IdUtil生成一个简单的UUID作为选项的ID questionOption.setQuestionOptionId(IdUtil.simpleUUID()); + // 将选项添加到列表中 questionOptionList.add(questionOption); } - // 把选项都存起来,然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds + // 将所有选项保存到数据库 questionOptionRepository.saveAll(questionOptionList); + // 初始化选项ID和答案选项ID的字符串 String questionOptionIds = ""; String questionAnswerOptionIds = ""; - // 经过上面的saveAll方法,所有的option的主键id都已经持久化了 + // 遍历选项创建对象列表,获取保存后的选项对象 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和答案选项ID question.setQuestionOptionIds(questionOptionIds); // 设置答案选项id组成的字符串 question.setQuestionAnswerOptionIds(questionAnswerOptionIds); - // 自己生成问题的id + // 为问题生成一个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()); @@ -230,27 +261,27 @@ public class ExamServiceImpl implements ExamService { return questionSelectionVo; } - /** - * 去除字符串最后的,防止split的时候出错 - * - * @param str 原始字符串 - * @return - */ + // 去除字符串末尾的"-"的方法 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( @@ -258,30 +289,35 @@ public class ExamServiceImpl implements ExamService { ).orElse(null) ).getQuestionTypeDescription() ); - // 获取当前问题的选项 + // 获取问题的选项ID字符串,去除末尾的"-",然后分割成数组 String optionIdsStr = trimMiddleLine(question.getQuestionOptionIds()); String[] optionIds = optionIdsStr.split("-"); - // 获取选项列表 + // 根据选项ID数组从数据库获取选项列表 List optionList = questionOptionRepository.findAllById(Arrays.asList(optionIds)); + // 设置问题详情对象的选项列表 questionDetailVo.setOptions(optionList); return questionDetailVo; } - + // 重写获取所有考试的方法 @Override public List getExamAll() { + // 从数据库获取所有考试对象 List examList = examRepository.findAll(); + // 将考试对象列表转换为ExamVo列表 return getExamVos(examList); } - + // 定义一个私有方法,接收一个Exam对象的列表作为参数,返回一个ExamVo对象的列表 private List getExamVos(List examList) { - // 需要自定义的exam列表 + // 初始化一个空的ExamVo列表,用于存放转换后的对象 List 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( @@ -290,385 +326,494 @@ public class ExamServiceImpl implements ExamService { ).getUserUsername() ); - // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上 + // 初始化单选题的列表,并设置到ExamVo对象的相应属性上 List radioQuestionVoList = new ArrayList<>(); + // 根据考试中的单选题ID字符串,分割成ID列表,并通过questionRepository查找对应的Question对象列表 List 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); // 考试中的问题肯定被选中的 + radioQuestionVo.setChecked(true);// 假设考试中的问题在初始化时都被视为已选中 radioQuestionVoList.add(radioQuestionVo); } examVo.setExamQuestionSelectVoRadioList(radioQuestionVoList); - - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上 + // 创建一个用于存储考试选择题Vo对象的列表 List checkQuestionVoList = new ArrayList<>(); + // 从数据库中获取所有需要检查(可能是单选或多选,但此处标记为检查题)的问题列表 + // 通过分割考试对象中的考试问题ID字符串(使用"-"作为分隔符),并将结果转换为List + // 然后调用questionRepository的findAllById方法获取Question对象列表 List checkQuestionList = questionRepository.findAllById( Arrays.asList(exam.getExamQuestionIdsCheck().split("-")) ); + // 遍历获取到的问题列表 for (Question question : checkQuestionList) { + // 为每个问题创建一个新的ExamQuestionSelectVo对象 ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo(); + // 使用BeanUtils工具类将Question对象的属性复制到新的ExamQuestionSelectVo对象中 BeanUtils.copyProperties(question, checkQuestionVo); - checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的 + // 设置选中状态为true(表示这个问题在考试中是已经被选中的) + checkQuestionVo.setChecked(true); // 考试中问题肯定被选中的 + // 将设置好的Vo对象添加到列表中 checkQuestionVoList.add(checkQuestionVo); } examVo.setExamQuestionSelectVoCheckList(checkQuestionVoList); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上 + // 从数据库中获取所有需要判断的问题列表 + // 通过分割考试对象中的考试问题ID字符串(使用"-"作为分隔符),并将结果转换为List + // 然后调用questionRepository的findAllById方法获取Question对象列表 List judgeQuestionVoList = new ArrayList<>(); List judgeQuestionList = questionRepository.findAllById( Arrays.asList(exam.getExamQuestionIdsJudge().split("-")) ); + // 遍历获取到的问题列表 for (Question question : judgeQuestionList) { + // 为每个问题创建一个新的ExamQuestionSelectVo对象 ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo(); + // 使用BeanUtils工具类将Question对象的属性复制到新的ExamQuestionSelectVo对象中 BeanUtils.copyProperties(question, judgeQuestionVo); - judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的 + // 设置选中状态为true + judgeQuestionVo.setChecked(true); + // 将设置好的Vo对象添加到列表中 judgeQuestionVoList.add(judgeQuestionVo); } examVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList); - // 把examVo加到examVoList中 + // 将处理好的ExamVo对象添加到列表中 examVoList.add(examVo); } + // 返回处理好的ExamVo列表 return examVoList; } - - - + // 重写父类或接口中的方法,用于获取考试问题类型的详细信息 @Override public ExamQuestionTypeVo getExamQuestionType() { + // 初始化一个ExamQuestionTypeVo对象,用于存储考试题型信息 ExamQuestionTypeVo examQuestionTypeVo = new ExamQuestionTypeVo(); - // 获取所有单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上 + // 获取所有的单选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上 List radioQuestionVoList = new ArrayList<>(); + // 从数据库中获取所有单选题(通过题型ID筛选),这里的QuestionEnum.RADIO.getId()返回单选题的ID List 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); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上 + // 初始化多选题的列表 List checkQuestionVoList = new ArrayList<>(); + // 从数据库中获取所有多选题(通过题型ID筛选),这里的QuestionEnum.CHECK.getId()返回多选题的ID List 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); - // 获取所有多选题列表,并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上 + // 初始化判断题的列表 List judgeQuestionVoList = new ArrayList<>(); + // 从数据库中获取所有判断题(通过题型ID筛选),这里的QuestionEnum.JUDGE.getId()返回判断题的ID List 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 radios = examCreateVo.getRadios(); List checks = examCreateVo.getChecks(); List 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 + // 分别设置单选、多选和判断题的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); - exam.setExamCreatorId(userId); // 考试的更新人为最新的创建人 - exam.setUpdateTime(new Date()); // 考试的更新日期要记录下 - + // 设置考试的更新人为当前的用户ID + exam.setExamCreatorId(userId); + // 记录考试的更新日期为当前时间 + exam.setUpdateTime(new Date()); + // 初始化字符串变量,用于存储不同类型的题目ID String radioIdsStr = ""; String checkIdsStr = ""; String judgeIdsStr = ""; + // 获取单选、多选和判断题的列表 List radios = examVo.getExamQuestionSelectVoRadioList(); List checks = examVo.getExamQuestionSelectVoCheckList(); List 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 + // 分别设置各类题目的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 getExamCardList() { + // 从数据库中获取所有的考试信息 List examList = examRepository.findAll(); + // 创建一个新的列表,用于存储转换后的考试卡片信息 List 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> answersMap) { - // 开始考试判分啦~~~ - // 1.首先获取考试对象和选项数组 - ExamDetailVo examDetailVo = getExamDetail(examId); - Exam exam = examDetailVo.getExam(); - // 2.然后获取该考试下所有的题目信息 - List questionIds = new ArrayList<>(); - // 2.1 题目id的数组 - List radioIdList = Arrays.asList(examDetailVo.getRadioIds()); - List checkIdList = Arrays.asList(examDetailVo.getCheckIds()); - List judgeIdList = Arrays.asList(examDetailVo.getJudgeIds()); - questionIds.addAll(radioIdList); - questionIds.addAll(checkIdList); - questionIds.addAll(judgeIdList); - // 2.2 每种题目的分数 - int radioScore = exam.getExamScoreRadio(); - int checkScore = exam.getExamScoreCheck(); - int judgeScore = exam.getExamScoreJudge(); - // 2.3 根据问题id的数组拿到所有的问题对象,供下面步骤用 - List questionList = questionRepository.findAllById(questionIds); - Map questionMap = new HashMap<>(); + // 开始考试判分~~~ + // 1. 获取考试详情对象和考试对象的选项数组 + ExamDetailVo examDetailVo = getExamDetail(examId);// 获取考试详情 + Exam exam = examDetailVo.getExam();// 获取考试对象 + // 2.然后获取该考试下所有题目信息 + List questionIds = new ArrayList<>();// 存储题目ID的列表 + // 2.1 获取各种题型的题目ID列表 + List radioIdList = Arrays.asList(examDetailVo.getRadioIds());// 单选题ID列表 + List checkIdList = Arrays.asList(examDetailVo.getCheckIds());// 多选题ID列表 + List 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 questionList = questionRepository.findAllById(questionIds);// 查询所有题目 + Map questionMap = new HashMap<>();// 使用Map存储题目ID和题目对象的映射 for (Question question : questionList) { - questionMap.put(question.getQuestionId(), question); + questionMap.put(question.getQuestionId(), question);// 将题目添加到Map中 } - // 3.根据正确答案和用户作答信息进行判分 - Set questionIdsAnswer = answersMap.keySet(); - // 存储当前考试每个题目的得分情况 + // 3. 根据正确答案,用户作答信息进行判分 + Set questionIdsAnswer = answersMap.keySet();// 获取用户作答的题目ID集合 + // 存储每个题目的得分情况 Map judgeMap = new HashMap<>(); - // 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情 - // 例子:题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3 + // 用于构建用户作答选项的字符串,用于查看考试详情 StringBuilder answerOptionIdsSb = new StringBuilder(); - // 用户此次考试的总分 + // 用户考试的总分 int totalScore = 0; + // 遍历用户作答的题目ID for (String questionId : questionIdsAnswer) { - // 获取用户作答地这个题的答案信息 + // 获取题目对象 Question question = questionMap.get(questionId); - // 获取答案选项 + // 获取题目正确答案的选项ID,并处理格式 String questionAnswerOptionIds = replaceLastSeparator(question.getQuestionAnswerOptionIds()); List questionAnswerOptionIdList = Arrays.asList(questionAnswerOptionIds.split("-")); - Collections.sort(questionAnswerOptionIdList); - String answerStr = listConcat(questionAnswerOptionIdList); - // 获取用户作答 + Collections.sort(questionAnswerOptionIdList);// 对选项进行排序 + String answerStr = listConcat(questionAnswerOptionIdList);// 将选项列表转换为字符串 + // 获取用户作答的选项ID列表,并处理格式 List questionUserOptionIdList = answersMap.get(questionId); - Collections.sort(questionUserOptionIdList); - String userStr = listConcat(questionUserOptionIdList); - // 判断questionAnswerOptionIds和answersMap里面的答案是否相等 + Collections.sort(questionUserOptionIdList);// 对选项进行排序 + String userStr = listConcat(questionUserOptionIdList);// 将选项列表转换为字符串 + // 判断用户作答是否正确 if (answerStr.equals(userStr)) { - // 说明题目作答正确,下面根据题型给分 + // 作答正确,根据题型设置分数 int score = 0; if (radioIdList.contains(questionId)) { - score = radioScore; + score = radioScore;// 单选题分数 } if (checkIdList.contains(questionId)) { - score = checkScore; + score = checkScore;// 多选题分数 } if (judgeIdList.contains(questionId)) { - score = judgeScore; + score = judgeScore;// 判断题分数 } - // 累计本次考试得分 + // 累加总分 totalScore += score; - // True代表题目答对 + // 记录答案正确及用户作答选项 answerOptionIdsSb.append(questionId + "@True_" + userStr + "$"); - judgeMap.put(questionId, score); + judgeMap.put(questionId, score);// 记录题目得分 } else { - // 说明题目作答错误,直接判零分,False代表题目答错 + // 作答错误,记0分 answerOptionIdsSb.append(questionId + "@False_" + userStr + "$"); - judgeMap.put(questionId, 0); + judgeMap.put(questionId, 0);// 记录题目得分为0 } } - // 4.计算得分,记录本次考试结果,存到ExamRecord中 - ExamRecord examRecord = new ExamRecord(); - examRecord.setExamRecordId(IdUtil.simpleUUID()); - examRecord.setExamId(examId); - // 注意去掉最后可能有的&_- + // 4. 计算得分,记录考试结果,并保存到数据库中 + ExamRecord examRecord = new ExamRecord();// 创建考试记录对象 + examRecord.setExamRecordId(IdUtil.simpleUUID());// 设置考试记录ID + examRecord.setExamId(examId);// 设置考试ID + // 设置用户作答选项字符串,注意处理格式 examRecord.setAnswerOptionIds(replaceLastSeparator(answerOptionIdsSb.toString())); - examRecord.setExamJoinerId(userId); - examRecord.setExamJoinDate(new Date()); - examRecord.setExamJoinScore(totalScore); - examRecordRepository.save(examRecord); - return examRecord; + examRecord.setExamJoinerId(userId);// 设置考生ID + examRecord.setExamJoinDate(new Date());// 设置考试日期 + examRecord.setExamJoinScore(totalScore);// 设置考试总分 + examRecordRepository.save(examRecord);// 保存考试记录到数据库 + return examRecord;// 返回考试记录对象 } - + // 重写方法,用于获取指定用户的考试记录列表 @Override public List getExamRecordList(String userId) { - // 获取指定用户下的考试记录列表 + // 通过用户ID从数据库中查询该用户参与的考试记录,按考试加入日期降序排列 List examRecordList = examRecordRepository.findByExamJoinerIdOrderByExamJoinDateDesc(userId); + // 创建一个用于存放转换后的考试记录视图对象的列表 List examRecordVoList = new ArrayList<>(); + // 遍历查询到的考试记录 for (ExamRecord examRecord : examRecordList) { - ExamRecordVo examRecordVo = new ExamRecordVo(); + 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> answersMap = new HashMap<>(); HashMap 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 + // 将题目和结果(True/False)存入resultsMap resultsMap.put(questionTitleAndResult[0], questionTitleAndResult[1]); } + // 设置答案和结果映射到视图对象中 recordDetailVo.setAnswersMap(answersMap); recordDetailVo.setResultsMap(resultsMap); - // 下面再计算正确答案的map + // 获取考试详情,用于获取所有题目的ID ExamDetailVo examDetailVo = getExamDetail(record.getExamId()); + // 初始化一个列表用于存放所有题目的ID List questionIdList = new ArrayList<>(); + // 将单选、多选、判断题的ID添加到列表中 questionIdList.addAll(Arrays.asList(examDetailVo.getRadioIds())); questionIdList.addAll(Arrays.asList(examDetailVo.getCheckIds())); questionIdList.addAll(Arrays.asList(examDetailVo.getJudgeIds())); - // 获取所有的问题对象 + // 根据题目ID列表从数据库中查询所有题目 List questionList = questionRepository.findAllById(questionIdList); + // 初始化一个哈希映射用于存储正确答案 HashMap> 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 替换掉最后一个-的字符串 + * @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 拼接好的字符串,记住要去掉最后面的- + * @param strList 包含要连接的字符串的列表。 + * @return 拼接好的字符串,确保已经去掉了末尾的-。 */ private String listConcat(List strList) { + // 创建一个StringBuilder对象,用于高效地构建最终的字符串 StringBuilder sb = new StringBuilder(); + // 遍历字符串列表。 for (String str : strList) { + // 将当前字符串添加到StringBuilder中。 sb.append(str); + // 在当前字符串后添加一个-作为分隔符。 sb.append("-"); } + // 调用replaceLastSeparator方法,移除StringBuilder中拼接字符串的最后一个-。 return replaceLastSeparator(sb.toString()); } }