package com.xuekairui.quiz.controller.user; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xuekairui.common.Result; import com.xuekairui.quiz.entity.QuizChapter; import com.xuekairui.quiz.entity.QuizQuestion; import com.xuekairui.quiz.entity.QuizUserRecord; import com.xuekairui.quiz.mapper.QuizChapterMapper; import com.xuekairui.quiz.mapper.QuizQuestionMapper; import com.xuekairui.quiz.mapper.QuizUserRecordMapper; import com.xuekairui.quiz.vo.UserChapterNodeVO; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; @RestController @RequestMapping("/api/quiz/chapter") @RequiredArgsConstructor public class UserQuizChapterController { private final QuizChapterMapper chapterMapper; private final QuizUserRecordMapper userRecordMapper; private final QuizQuestionMapper questionMapper; @GetMapping("/list") public Result> list(@RequestParam Long subjectId, HttpServletRequest request) { Long userId = (Long) request.getAttribute("userId"); List chapters = chapterMapper.selectList( new LambdaQueryWrapper() .eq(QuizChapter::getSubjectId, subjectId) .orderByAsc(QuizChapter::getSort)); // 后台在"科目"下新增的章 parentId=科目ID;移动端省略科目层,统一归一化为 0 作为根节点 for (QuizChapter ch : chapters) { if (subjectId.equals(ch.getParentId())) { ch.setParentId(0L); } } // 实时查询各考点(exam_point_id)直接关联的题目数 Map directCountMap = getDirectQuestionCounts(subjectId); List tree = buildUserTree(chapters, 0L, userId, directCountMap); return Result.success(tree); } private List buildUserTree(List chapters, Long parentId, Long userId, Map directCountMap) { List result = new ArrayList<>(); for (QuizChapter ch : chapters) { if (Objects.equals(ch.getParentId(), parentId)) { long doneCount = userRecordMapper.selectCount( new LambdaQueryWrapper() .eq(QuizUserRecord::getUserId, userId) .eq(QuizUserRecord::getExamPointId, ch.getId()) .eq(QuizUserRecord::getStatus, 1)); List children = null; // 只展开到"节"(level=3),不再往下展示"考点"(level=4) if (ch.getLevel() != null && ch.getLevel() < 3) { List subChildren = buildUserTree(chapters, ch.getId(), userId, directCountMap); if (!subChildren.isEmpty()) { children = subChildren; } } // 汇总本节点及所有子孙节点的题目数(实时查询,不依赖缓存字段) int totalQuestionCount = sumQuestionCount(chapters, ch.getId(), directCountMap) + directCountMap.getOrDefault(ch.getId(), 0); result.add(UserChapterNodeVO.builder() .id(ch.getId()) .name(ch.getName()) .level(ch.getLevel()) .questionCount(totalQuestionCount) .doneCount(doneCount) .children(children) .build()); } } return result; } /** 递归汇总所有子孙节点的题目数(实时查询) */ private int sumQuestionCount(List chapters, Long parentId, Map directCountMap) { int sum = 0; for (QuizChapter ch : chapters) { if (Objects.equals(ch.getParentId(), parentId)) { sum += directCountMap.getOrDefault(ch.getId(), 0) + sumQuestionCount(chapters, ch.getId(), directCountMap); } } return sum; } /** 批量查询指定科目下各考点直接关联的练习题数(不含真题) */ private Map getDirectQuestionCounts(Long subjectId) { List questions = questionMapper.selectList( new LambdaQueryWrapper() .eq(QuizQuestion::getSubjectId, subjectId) .eq(QuizQuestion::getIsReal, 0) .select(QuizQuestion::getExamPointId) ); Map map = new HashMap<>(); for (QuizQuestion q : questions) { if (q.getExamPointId() != null) { map.merge(q.getExamPointId(), 1, Integer::sum); } } return map; } }