|
|
@@ -25,6 +25,7 @@ import com.xuekairui.quiz.vo.StudyBookQuestionVO;
|
|
|
import com.xuekairui.quiz.vo.StudyOverviewVO;
|
|
|
import com.xuekairui.quiz.vo.StudyQuestionVO;
|
|
|
import com.xuekairui.quiz.vo.SubjectSimpleVO;
|
|
|
+import com.xuekairui.quiz.util.QuizDedupUtils;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -57,12 +58,15 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
.orderByAsc(QuizSubject::getSort)
|
|
|
);
|
|
|
|
|
|
+ // 实时查询各科目题目数
|
|
|
+ Map<Long, Integer> subjectCountMap = getSubjectQuestionCounts();
|
|
|
+
|
|
|
List<SubjectSimpleVO> result = new ArrayList<>();
|
|
|
for (QuizSubject s : subjects) {
|
|
|
result.add(SubjectSimpleVO.builder()
|
|
|
.id(s.getId())
|
|
|
.name(s.getName())
|
|
|
- .questionCount(s.getQuestionCount())
|
|
|
+ .questionCount(subjectCountMap.getOrDefault(s.getId(), 0))
|
|
|
.build());
|
|
|
}
|
|
|
return result;
|
|
|
@@ -81,7 +85,10 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
.orderByAsc(QuizChapter::getSort)
|
|
|
);
|
|
|
|
|
|
- List<ChapterNodeVO> tree = buildChapterTree(chapters, 0L);
|
|
|
+ // 实时查询各考点题目数
|
|
|
+ Map<Long, Integer> directCountMap = getDirectQuestionCounts();
|
|
|
+
|
|
|
+ List<ChapterNodeVO> tree = buildChapterTree(chapters, 0L, directCountMap);
|
|
|
|
|
|
return ChapterTreeVO.builder()
|
|
|
.subjectId(subject.getId())
|
|
|
@@ -90,16 +97,21 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
.build();
|
|
|
}
|
|
|
|
|
|
- private List<ChapterNodeVO> buildChapterTree(List<QuizChapter> allChapters, Long parentId) {
|
|
|
+ private List<ChapterNodeVO> buildChapterTree(List<QuizChapter> allChapters, Long parentId, Map<Long, Integer> directCountMap) {
|
|
|
List<ChapterNodeVO> tree = new ArrayList<>();
|
|
|
for (QuizChapter ch : allChapters) {
|
|
|
if (Objects.equals(ch.getParentId(), parentId)) {
|
|
|
- List<ChapterNodeVO> children = buildChapterTree(allChapters, ch.getId());
|
|
|
+ List<ChapterNodeVO> children = buildChapterTree(allChapters, ch.getId(), directCountMap);
|
|
|
+ // 递归汇总:自身直接关联的题目数 + 所有子节点的题目数
|
|
|
+ int count = directCountMap.getOrDefault(ch.getId(), 0);
|
|
|
+ for (ChapterNodeVO child : children) {
|
|
|
+ count += child.getQuestionCount();
|
|
|
+ }
|
|
|
tree.add(ChapterNodeVO.builder()
|
|
|
.id(ch.getId())
|
|
|
.name(ch.getName())
|
|
|
.level(ch.getLevel())
|
|
|
- .questionCount(ch.getQuestionCount())
|
|
|
+ .questionCount(count)
|
|
|
.children(children.isEmpty() ? null : children)
|
|
|
.build());
|
|
|
}
|
|
|
@@ -108,8 +120,8 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public ChapterQuestionsVO getChapterQuestions(Long userId, Long chapterId) {
|
|
|
- QuizChapter chapter = chapterMapper.selectById(chapterId);
|
|
|
+ public ChapterQuestionsVO getChapterQuestions(Long userId, Long examPointId) {
|
|
|
+ QuizChapter chapter = chapterMapper.selectById(examPointId);
|
|
|
if (chapter == null) {
|
|
|
throw new BusinessException(ErrorCode.QUIZ_CHAPTER_NOT_FOUND);
|
|
|
}
|
|
|
@@ -117,7 +129,7 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
// 获取该考点下的练习题(非真题)
|
|
|
List<QuizQuestion> questions = questionMapper.selectList(
|
|
|
new LambdaQueryWrapper<QuizQuestion>()
|
|
|
- .eq(QuizQuestion::getChapterId, chapterId)
|
|
|
+ .eq(QuizQuestion::getExamPointId, examPointId)
|
|
|
.eq(QuizQuestion::getIsReal, 0)
|
|
|
.orderByAsc(QuizQuestion::getId)
|
|
|
);
|
|
|
@@ -126,7 +138,7 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
long doneCount = userRecordMapper.selectCount(
|
|
|
new LambdaQueryWrapper<QuizUserRecord>()
|
|
|
.eq(QuizUserRecord::getUserId, userId)
|
|
|
- .eq(QuizUserRecord::getChapterId, chapterId)
|
|
|
+ .eq(QuizUserRecord::getExamPointId, examPointId)
|
|
|
.eq(QuizUserRecord::getAnswerType, 1)
|
|
|
.eq(QuizUserRecord::getStatus, 1)
|
|
|
);
|
|
|
@@ -143,8 +155,8 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
}
|
|
|
|
|
|
return ChapterQuestionsVO.builder()
|
|
|
- .chapterId(chapterId)
|
|
|
- .chapterName(chapter.getName())
|
|
|
+ .examPointId(examPointId)
|
|
|
+ .examPointName(chapter.getName())
|
|
|
.questionCount(questionList.size())
|
|
|
.hasDone(doneCount > 0)
|
|
|
.questions(questionList)
|
|
|
@@ -153,11 +165,12 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
|
|
|
@Override
|
|
|
@Transactional
|
|
|
- public PracticeSubmitVO submitChapterPractice(Long userId, Long subjectId, Long chapterId, List<AnswerItem> answers) {
|
|
|
+ public PracticeSubmitVO submitChapterPractice(Long userId, Long subjectId, Long examPointId, List<AnswerItem> answers) {
|
|
|
int correctCount = 0;
|
|
|
int wrongCount = 0;
|
|
|
List<PracticeResultVO> results = new ArrayList<>();
|
|
|
List<Map<String, Object>> answerRecords = new ArrayList<>();
|
|
|
+ Long resolvedSubjectId = subjectId;
|
|
|
|
|
|
for (AnswerItem ans : answers) {
|
|
|
Long questionId = ans.getQuestionId();
|
|
|
@@ -167,14 +180,22 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
QuizQuestion question = questionMapper.selectById(questionId);
|
|
|
if (question == null) continue;
|
|
|
|
|
|
- boolean isCorrect = question.getAnswer().equalsIgnoreCase(userAnswer);
|
|
|
+ // subjectId 为空时从题目获取(subject_id 列为 NOT NULL)
|
|
|
+ if (resolvedSubjectId == null) {
|
|
|
+ resolvedSubjectId = question.getSubjectId();
|
|
|
+ }
|
|
|
+
|
|
|
+ String correctAnswer = question.getAnswer();
|
|
|
+ // 去除首尾空格后比较,避免前端传入的答案含多余空格导致误判
|
|
|
+ boolean isCorrect = correctAnswer != null && userAnswer != null
|
|
|
+ && correctAnswer.trim().equalsIgnoreCase(userAnswer.trim());
|
|
|
if (isCorrect) correctCount++;
|
|
|
else wrongCount++;
|
|
|
|
|
|
results.add(PracticeResultVO.builder()
|
|
|
.questionId(questionId)
|
|
|
.isCorrect(isCorrect ? 1 : 0)
|
|
|
- .correctAnswer(question.getAnswer())
|
|
|
+ .correctAnswer(correctAnswer)
|
|
|
.analysis(question.getAnalysis())
|
|
|
.build());
|
|
|
|
|
|
@@ -191,21 +212,96 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
? BigDecimal.valueOf(correctCount * 100.0 / total).setScale(0, RoundingMode.HALF_UP)
|
|
|
: BigDecimal.ZERO;
|
|
|
|
|
|
- QuizUserRecord record = QuizUserRecord.builder()
|
|
|
- .userId(userId)
|
|
|
- .subjectId(subjectId)
|
|
|
- .chapterId(chapterId)
|
|
|
- .answerType(1)
|
|
|
- .totalCount(total)
|
|
|
+ if (total > 0) {
|
|
|
+ QuizUserRecord record = QuizUserRecord.builder()
|
|
|
+ .userId(userId)
|
|
|
+ .subjectId(resolvedSubjectId)
|
|
|
+ .examPointId(examPointId)
|
|
|
+ .answerType(1)
|
|
|
+ .totalCount(total)
|
|
|
+ .correctCount(correctCount)
|
|
|
+ .wrongCount(wrongCount)
|
|
|
+ .answers(answerRecords)
|
|
|
+ .status(1)
|
|
|
+ .startTime(LocalDateTime.now())
|
|
|
+ .endTime(LocalDateTime.now())
|
|
|
+ .build();
|
|
|
+ userRecordMapper.insert(record);
|
|
|
+ }
|
|
|
+
|
|
|
+ return PracticeSubmitVO.builder()
|
|
|
.correctCount(correctCount)
|
|
|
.wrongCount(wrongCount)
|
|
|
.correctRate(correctRate)
|
|
|
- .answers(answerRecords)
|
|
|
- .status(1)
|
|
|
- .startTime(LocalDateTime.now())
|
|
|
- .endTime(LocalDateTime.now())
|
|
|
+ .results(results)
|
|
|
.build();
|
|
|
- userRecordMapper.insert(record);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public PracticeSubmitVO redoWrong(Long userId, List<AnswerItem> answers) {
|
|
|
+ int correctCount = 0;
|
|
|
+ int wrongCount = 0;
|
|
|
+ List<PracticeResultVO> results = new ArrayList<>();
|
|
|
+ List<Map<String, Object>> answerRecords = new ArrayList<>();
|
|
|
+ Long subjectId = null;
|
|
|
+
|
|
|
+ for (AnswerItem ans : answers) {
|
|
|
+ Long questionId = ans.getQuestionId();
|
|
|
+ String userAnswer = ans.getUserAnswer();
|
|
|
+ Integer timeSpent = ans.getTimeSpent() != null ? ans.getTimeSpent() : 0;
|
|
|
+
|
|
|
+ QuizQuestion question = questionMapper.selectById(questionId);
|
|
|
+ if (question == null) continue;
|
|
|
+
|
|
|
+ // 从第一道题获取科目ID(subject_id 列为 NOT NULL)
|
|
|
+ if (subjectId == null) {
|
|
|
+ subjectId = question.getSubjectId();
|
|
|
+ }
|
|
|
+
|
|
|
+ String correctAnswer = question.getAnswer();
|
|
|
+ // 去除首尾空格后比较,避免前端传入的答案含多余空格导致误判
|
|
|
+ boolean isCorrect = correctAnswer != null && userAnswer != null
|
|
|
+ && correctAnswer.trim().equalsIgnoreCase(userAnswer.trim());
|
|
|
+ if (isCorrect) correctCount++;
|
|
|
+ else wrongCount++;
|
|
|
+
|
|
|
+ results.add(PracticeResultVO.builder()
|
|
|
+ .questionId(questionId)
|
|
|
+ .isCorrect(isCorrect ? 1 : 0)
|
|
|
+ .correctAnswer(correctAnswer)
|
|
|
+ .analysis(question.getAnalysis())
|
|
|
+ .build());
|
|
|
+
|
|
|
+ Map<String, Object> record = new LinkedHashMap<>();
|
|
|
+ record.put("questionId", questionId);
|
|
|
+ record.put("userAnswer", userAnswer);
|
|
|
+ record.put("isCorrect", isCorrect ? 1 : 0);
|
|
|
+ record.put("timeSpent", timeSpent);
|
|
|
+ answerRecords.add(record);
|
|
|
+ }
|
|
|
+
|
|
|
+ int total = correctCount + wrongCount;
|
|
|
+ BigDecimal correctRate = total > 0
|
|
|
+ ? BigDecimal.valueOf(correctCount * 100.0 / total).setScale(0, RoundingMode.HALF_UP)
|
|
|
+ : BigDecimal.ZERO;
|
|
|
+
|
|
|
+ // 只有有答题记录时才插入(subject_id 为 NOT NULL,无题目时无法插入)
|
|
|
+ if (total > 0) {
|
|
|
+ QuizUserRecord record = QuizUserRecord.builder()
|
|
|
+ .userId(userId)
|
|
|
+ .subjectId(subjectId)
|
|
|
+ .answerType(3)
|
|
|
+ .totalCount(total)
|
|
|
+ .correctCount(correctCount)
|
|
|
+ .wrongCount(wrongCount)
|
|
|
+ .answers(answerRecords)
|
|
|
+ .status(1)
|
|
|
+ .startTime(LocalDateTime.now())
|
|
|
+ .endTime(LocalDateTime.now())
|
|
|
+ .build();
|
|
|
+ userRecordMapper.insert(record);
|
|
|
+ }
|
|
|
|
|
|
return PracticeSubmitVO.builder()
|
|
|
.correctCount(correctCount)
|
|
|
@@ -233,6 +329,9 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
})
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
+ // 实时查询各考点题目数
|
|
|
+ Map<Long, Integer> directCountMap = getDirectQuestionCounts();
|
|
|
+
|
|
|
int completedCount = 0;
|
|
|
List<ChapterProgressItemVO> progressList = new ArrayList<>();
|
|
|
|
|
|
@@ -240,7 +339,7 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
long doneCount = userRecordMapper.selectCount(
|
|
|
new LambdaQueryWrapper<QuizUserRecord>()
|
|
|
.eq(QuizUserRecord::getUserId, userId)
|
|
|
- .eq(QuizUserRecord::getChapterId, ch.getId())
|
|
|
+ .eq(QuizUserRecord::getExamPointId, ch.getId())
|
|
|
.eq(QuizUserRecord::getAnswerType, 1)
|
|
|
.eq(QuizUserRecord::getStatus, 1)
|
|
|
);
|
|
|
@@ -249,9 +348,9 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
if (completed) completedCount++;
|
|
|
|
|
|
progressList.add(ChapterProgressItemVO.builder()
|
|
|
- .chapterId(ch.getId())
|
|
|
- .chapterName(ch.getName())
|
|
|
- .questionCount(ch.getQuestionCount())
|
|
|
+ .examPointId(ch.getId())
|
|
|
+ .examPointName(ch.getName())
|
|
|
+ .questionCount(directCountMap.getOrDefault(ch.getId(), 0))
|
|
|
.completed(completed)
|
|
|
.practiceCount(doneCount)
|
|
|
.build());
|
|
|
@@ -272,9 +371,22 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public StudyOverviewVO getOverview(Long userId) {
|
|
|
+ public StudyOverviewVO getOverview(Long userId, Long subjectId) {
|
|
|
// 按题去重,以最新一次答题状态统计做对/做错题数
|
|
|
- Map<Long, Integer> latestIsCorrect = getLatestIsCorrect(userId, null);
|
|
|
+ Map<Long, Integer> latestIsCorrect = getLatestIsCorrect(userId, subjectId);
|
|
|
+
|
|
|
+ // 过滤掉已删除的题目(只统计数据库中仍存在的题目)
|
|
|
+ Set<Long> existingQuestionIds = new LinkedHashSet<>(latestIsCorrect.keySet());
|
|
|
+ if (!existingQuestionIds.isEmpty()) {
|
|
|
+ List<QuizQuestion> existing = questionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizQuestion>()
|
|
|
+ .in(QuizQuestion::getId, existingQuestionIds)
|
|
|
+ .select(QuizQuestion::getId)
|
|
|
+ );
|
|
|
+ Set<Long> validIds = existing.stream().map(QuizQuestion::getId).collect(Collectors.toSet());
|
|
|
+ latestIsCorrect.keySet().retainAll(validIds);
|
|
|
+ }
|
|
|
+
|
|
|
int totalCorrect = 0;
|
|
|
int totalWrong = 0;
|
|
|
for (Integer isCorrect : latestIsCorrect.values()) {
|
|
|
@@ -287,41 +399,50 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
new LambdaQueryWrapper<QuizUserRecord>()
|
|
|
.eq(QuizUserRecord::getUserId, userId)
|
|
|
.eq(QuizUserRecord::getAnswerType, 99)
|
|
|
+ .eq(subjectId != null && subjectId != 0L, QuizUserRecord::getSubjectId, subjectId)
|
|
|
);
|
|
|
|
|
|
+ int totalDone = totalCorrect + totalWrong;
|
|
|
+ BigDecimal correctRate = totalDone > 0
|
|
|
+ ? BigDecimal.valueOf(totalCorrect * 100.0 / totalDone).setScale(0, RoundingMode.HALF_UP)
|
|
|
+ : BigDecimal.ZERO;
|
|
|
+
|
|
|
return StudyOverviewVO.builder()
|
|
|
.correctCount(totalCorrect)
|
|
|
.wrongCount(totalWrong)
|
|
|
.collectedCount((int) collectedCount)
|
|
|
- .totalDone(totalCorrect + totalWrong)
|
|
|
+ .totalDone(totalDone)
|
|
|
+ .correctRate(correctRate)
|
|
|
.build();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取用户每题最新一次答题状态(做对=1 / 做错=0)。
|
|
|
* 记录按 createTime 倒序查询,首次出现的题目即为最新状态,后续重复题目跳过。
|
|
|
+ * 按题目实际的 subjectId 过滤(而非 record.subjectId),避免错题重做时多科目混记导致统计错误。
|
|
|
*/
|
|
|
private Map<Long, Integer> getLatestIsCorrect(Long userId, Long subjectId) {
|
|
|
List<QuizUserRecord> records = userRecordMapper.selectList(
|
|
|
new LambdaQueryWrapper<QuizUserRecord>()
|
|
|
.eq(QuizUserRecord::getUserId, userId)
|
|
|
- .eq(QuizUserRecord::getAnswerType, 1)
|
|
|
+ .ne(QuizUserRecord::getAnswerType, 99)
|
|
|
.eq(QuizUserRecord::getStatus, 1)
|
|
|
- .eq(subjectId != null && subjectId != 0L, QuizUserRecord::getSubjectId, subjectId)
|
|
|
.orderByDesc(QuizUserRecord::getCreateTime)
|
|
|
);
|
|
|
-
|
|
|
- Map<Long, Integer> latest = new LinkedHashMap<>();
|
|
|
- for (QuizUserRecord r : records) {
|
|
|
- if (r.getAnswers() == null) continue;
|
|
|
- for (Map<String, Object> ans : r.getAnswers()) {
|
|
|
- if (ans.get("questionId") == null) continue;
|
|
|
- Long qid = Long.valueOf(ans.get("questionId").toString());
|
|
|
- if (latest.containsKey(qid)) continue;
|
|
|
- Integer isCorrect = ans.get("isCorrect") != null ? Integer.valueOf(ans.get("isCorrect").toString()) : null;
|
|
|
- latest.put(qid, isCorrect);
|
|
|
- }
|
|
|
+ Map<Long, Integer> latest = QuizDedupUtils.extractLatestAnswerMap(records);
|
|
|
+
|
|
|
+ // 按题目实际的 subjectId 过滤
|
|
|
+ if (subjectId != null && subjectId != 0L && !latest.isEmpty()) {
|
|
|
+ List<QuizQuestion> questions = questionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizQuestion>()
|
|
|
+ .in(QuizQuestion::getId, latest.keySet())
|
|
|
+ .eq(QuizQuestion::getSubjectId, subjectId)
|
|
|
+ .select(QuizQuestion::getId)
|
|
|
+ );
|
|
|
+ Set<Long> validIds = questions.stream().map(QuizQuestion::getId).collect(Collectors.toSet());
|
|
|
+ latest.keySet().retainAll(validIds);
|
|
|
}
|
|
|
+
|
|
|
return latest;
|
|
|
}
|
|
|
|
|
|
@@ -359,6 +480,18 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
// 做对/做错的题目:按题去重,以最新一次答题状态为准
|
|
|
Map<Long, Integer> latestIsCorrect = getLatestIsCorrect(userId, subjectId);
|
|
|
|
|
|
+ // 过滤掉已删除的题目(只统计数据库中仍存在的题目)
|
|
|
+ Set<Long> existingQuestionIds = new LinkedHashSet<>(latestIsCorrect.keySet());
|
|
|
+ if (!existingQuestionIds.isEmpty()) {
|
|
|
+ List<QuizQuestion> existing = questionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizQuestion>()
|
|
|
+ .in(QuizQuestion::getId, existingQuestionIds)
|
|
|
+ .select(QuizQuestion::getId)
|
|
|
+ );
|
|
|
+ Set<Long> validIds = existing.stream().map(QuizQuestion::getId).collect(Collectors.toSet());
|
|
|
+ latestIsCorrect.keySet().retainAll(validIds);
|
|
|
+ }
|
|
|
+
|
|
|
Set<Long> questionIds = new LinkedHashSet<>();
|
|
|
for (Map.Entry<Long, Integer> e : latestIsCorrect.entrySet()) {
|
|
|
Integer isCorrect = e.getValue();
|
|
|
@@ -376,10 +509,37 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
.limit(pageSize)
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
+ // 批量查询用户收藏的题目ID(1次查询代替N次)
|
|
|
+ Set<Long> collectedQuestionIds = new LinkedHashSet<>();
|
|
|
+ Map<Long, LocalDateTime> collectedTimeMap = new LinkedHashMap<>();
|
|
|
+ List<QuizUserRecord> collectRecords = userRecordMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizUserRecord>()
|
|
|
+ .eq(QuizUserRecord::getUserId, userId)
|
|
|
+ .eq(QuizUserRecord::getAnswerType, 99)
|
|
|
+ .orderByDesc(QuizUserRecord::getCreateTime)
|
|
|
+ );
|
|
|
+ for (QuizUserRecord r : collectRecords) {
|
|
|
+ if (r.getAnswers() != null) {
|
|
|
+ for (Map<String, Object> ans : r.getAnswers()) {
|
|
|
+ if (ans.get("questionId") != null) {
|
|
|
+ Long qid = Long.valueOf(ans.get("questionId").toString());
|
|
|
+ collectedQuestionIds.add(qid);
|
|
|
+ if (!collectedTimeMap.containsKey(qid)) {
|
|
|
+ collectedTimeMap.put(qid, r.getCreateTime());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
for (Long qId : pagedIds) {
|
|
|
QuizQuestion q = questionMapper.selectById(qId);
|
|
|
if (q != null) {
|
|
|
- questionList.add(buildQuestionItem(q));
|
|
|
+ StudyBookQuestionVO item = buildQuestionItem(q);
|
|
|
+ if (collectedQuestionIds.contains(qId)) {
|
|
|
+ item.setCollectedAt(collectedTimeMap.get(qId));
|
|
|
+ }
|
|
|
+ questionList.add(item);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -391,16 +551,67 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
}
|
|
|
|
|
|
private StudyBookQuestionVO buildQuestionItem(QuizQuestion q) {
|
|
|
+ String subjectName = null;
|
|
|
+ if (q.getSubjectId() != null) {
|
|
|
+ QuizSubject subject = subjectMapper.selectById(q.getSubjectId());
|
|
|
+ if (subject != null) {
|
|
|
+ subjectName = subject.getName();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 配伍题/综合分析题:options 为空时,从 sharedOptions 解析选项
|
|
|
+ List<Map<String, Object>> options = q.getOptions();
|
|
|
+ if ((options == null || options.isEmpty()) && q.getSharedOptions() != null && !q.getSharedOptions().isEmpty()) {
|
|
|
+ options = parseSharedOptions(q.getSharedOptions());
|
|
|
+ }
|
|
|
+
|
|
|
return StudyBookQuestionVO.builder()
|
|
|
.id(q.getId())
|
|
|
+ .subjectId(q.getSubjectId())
|
|
|
+ .subjectName(subjectName)
|
|
|
.questionType(q.getQuestionType())
|
|
|
.questionTypeName(getTypeName(q.getQuestionType()))
|
|
|
+ .matchingGroup(q.getMatchingGroup())
|
|
|
+ .sharedOptions(q.getSharedOptions())
|
|
|
.stem(q.getStem())
|
|
|
- .options(q.getOptions())
|
|
|
+ .options(options)
|
|
|
.answer(q.getAnswer())
|
|
|
.analysis(q.getAnalysis())
|
|
|
.build();
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析共用选项文本为结构化选项列表
|
|
|
+ * 格式:A.内容B.内容C.内容...
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> parseSharedOptions(String sharedOptions) {
|
|
|
+ List<Map<String, Object>> options = new ArrayList<>();
|
|
|
+ if (sharedOptions == null || sharedOptions.isEmpty()) {
|
|
|
+ return options;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按字母+点分割:A.xxx B.xxx C.xxx ...
|
|
|
+ String[] parts = sharedOptions.split("(?=[A-E]\\.?)");
|
|
|
+ for (String part : parts) {
|
|
|
+ part = part.trim();
|
|
|
+ if (part.isEmpty()) continue;
|
|
|
+
|
|
|
+ // 提取选项字母和内容
|
|
|
+ String key = "";
|
|
|
+ String content = part;
|
|
|
+ if (part.length() >= 1) {
|
|
|
+ key = String.valueOf(part.charAt(0));
|
|
|
+ content = part.substring(1).replaceFirst("^\\.?", "").trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> option = new LinkedHashMap<>();
|
|
|
+ option.put("key", key);
|
|
|
+ option.put("content", content);
|
|
|
+ options.add(option);
|
|
|
+ }
|
|
|
+
|
|
|
+ return options;
|
|
|
+ }
|
|
|
|
|
|
private String getTypeName(Integer type) {
|
|
|
if (type == null) return "";
|
|
|
@@ -412,4 +623,40 @@ public class StudyBookServiceImpl implements StudyBookService {
|
|
|
default -> "";
|
|
|
};
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实时查询各考点(exam_point_id)直接关联的练习题数(不含真题)
|
|
|
+ */
|
|
|
+ private Map<Long, Integer> getDirectQuestionCounts() {
|
|
|
+ List<QuizQuestion> questions = questionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizQuestion>()
|
|
|
+ .eq(QuizQuestion::getIsReal, 0)
|
|
|
+ .select(QuizQuestion::getExamPointId)
|
|
|
+ );
|
|
|
+ Map<Long, Integer> map = new LinkedHashMap<>();
|
|
|
+ for (QuizQuestion q : questions) {
|
|
|
+ if (q.getExamPointId() != null) {
|
|
|
+ map.merge(q.getExamPointId(), 1, Integer::sum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实时查询各科目练习题数(不含真题)
|
|
|
+ */
|
|
|
+ private Map<Long, Integer> getSubjectQuestionCounts() {
|
|
|
+ List<QuizQuestion> questions = questionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QuizQuestion>()
|
|
|
+ .eq(QuizQuestion::getIsReal, 0)
|
|
|
+ .select(QuizQuestion::getSubjectId)
|
|
|
+ );
|
|
|
+ Map<Long, Integer> map = new LinkedHashMap<>();
|
|
|
+ for (QuizQuestion q : questions) {
|
|
|
+ if (q.getSubjectId() != null) {
|
|
|
+ map.merge(q.getSubjectId(), 1, Integer::sum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
}
|