题库、练习与错题本代码阅读

题库、练习与错题本代码阅读地图

你下一步适合阅读 "学生查看题目 → 提交答案 → 自动判分 → 记录错题 → 发放积分"。登录模块回答"你是谁",这条链路回答"确定身份后,系统怎样处理你的学习行为"。

1. 先看业务接力图

下面展示提交一道练习题的主要流程;身份恢复仍由你已经读过的认证模块完成。
PointService:积分记账员 WrongBookService:错题管理员 ObjectiveJudge:阅卷员 数据库 CurrentUser:身份取件员 QuestionController:调度员 浏览器 PointService:积分记账员 WrongBookService:错题管理员 ObjectiveJudge:阅卷员 数据库 CurrentUser:身份取件员 QuestionController:调度员 浏览器 #mermaid-svg-qHHOwvOeAHIwIayK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qHHOwvOeAHIwIayK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qHHOwvOeAHIwIayK .error-icon{fill:#552222;}#mermaid-svg-qHHOwvOeAHIwIayK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qHHOwvOeAHIwIayK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qHHOwvOeAHIwIayK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qHHOwvOeAHIwIayK .marker.cross{stroke:#333333;}#mermaid-svg-qHHOwvOeAHIwIayK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qHHOwvOeAHIwIayK p{margin:0;}#mermaid-svg-qHHOwvOeAHIwIayK .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qHHOwvOeAHIwIayK text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-qHHOwvOeAHIwIayK .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qHHOwvOeAHIwIayK .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-qHHOwvOeAHIwIayK .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-qHHOwvOeAHIwIayK .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-qHHOwvOeAHIwIayK #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-qHHOwvOeAHIwIayK .sequenceNumber{fill:white;}#mermaid-svg-qHHOwvOeAHIwIayK #sequencenumber{fill:#333;}#mermaid-svg-qHHOwvOeAHIwIayK #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-qHHOwvOeAHIwIayK .messageText{fill:#333;stroke:none;}#mermaid-svg-qHHOwvOeAHIwIayK .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qHHOwvOeAHIwIayK .labelText,#mermaid-svg-qHHOwvOeAHIwIayK .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-qHHOwvOeAHIwIayK .loopText,#mermaid-svg-qHHOwvOeAHIwIayK .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-qHHOwvOeAHIwIayK .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qHHOwvOeAHIwIayK .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-qHHOwvOeAHIwIayK .noteText,#mermaid-svg-qHHOwvOeAHIwIayK .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-qHHOwvOeAHIwIayK .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qHHOwvOeAHIwIayK .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qHHOwvOeAHIwIayK .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qHHOwvOeAHIwIayK .actorPopupMenu{position:absolute;}#mermaid-svg-qHHOwvOeAHIwIayK .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-qHHOwvOeAHIwIayK .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qHHOwvOeAHIwIayK .actor-man circle,#mermaid-svg-qHHOwvOeAHIwIayK line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-qHHOwvOeAHIwIayK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 检查是否允许练习这道题 opt 普通练习答对,且不处于当前错题本中 POST /api/student/questions/{id}/practice student(),确认学生身份 返回 LoginUser 同步历史错题、查询当前错题状态 读取题目、标准答案和分值 score(),计算得分 返回满分或零分 保存本次 practice_record recordPracticeResult(),更新错题状态 返回错题状态及是否可移出 检查重复来源、每日额度并记录奖励 返回实际奖励及积分信息 返回正确性、答案、解析、积分和错题状态

2. 阅读哪些文件,谁负责什么

文件 角色标签 主要职责
QuestionController.java 调度员 接收题库和练习请求,检查权限,组织判分、保存、错题和积分处理。
ObjectiveJudge.java 阅卷员 比较标准答案与学生答案,计算单题得分,不读写数据库。
WrongBookService.java 错题管理员 维护待复习、待移出、已掌握三种状态,补齐历史错题。
PointService.java 积分记账员 查询余额、限制每日奖励、避免同一来源重复发分。
GradeRules.java 年级规则表 判断下一年级,以及当前题目是否属于可积分年级范围。

不要从 QuestionController 第一行一直读到底。 先读 studentDetail() 和 submitPractice(),遇到方法调用再跳到对应类;教师分页、随机组卷和批量录题可以留到下一轮。

3. 按四条链路阅读

链路 接口与入口方法 应抓住的主干
查看练习列表 GET /api/student/questions → studentList() 确认学生身份 → 同步历史错题 → 确定当前或下一年级 → 查询公开、审核通过、用途为 PRACTICE 的题目 → 附加积分和错题提示。九年级没有下一年级,默认查询下一年级时返回空列表。
打开一道题 GET /api/student/questions/{id} → studentDetail() 读取题目 → 判断是否为当前错题 → ensureStudentPracticeAccess() 校验资格 → 补充展示字段。当前错题允许继续复习;普通练习检查审核、公开性、用途和年级。
提交答案 POST /api/student/questions/{id}/practice → submitPractice() 重新检查资格 → 判分 → 无论对错都保存练习记录 → 更新错题 → 根据分支决定是否发积分 → 返回结果。能打开题目不代表提交时可以省略权限校验。
查看与移出错题 wrongQuestions()、wrongBookHistory()、removeWrongQuestion() 当前列表显示 IN_BOOK 和 READY_TO_REMOVE;历史显示 MASTERED;移出接口只允许 READY_TO_REMOVE 转为 MASTERED,不直接删除记录。

题目列表通过 questionSelect(false) 不返回标准答案和解析;但 studentDetail() 的 SQL 已经返回 analysis,没有返回标准答案 。提交后返回 correctAnswer 和 analysis。阅读时要以实际查询字段为准,不能笼统说"所有解析都在答题后才返回"。

4. 提交答案中的三个关键分支

场景 错题处理 积分处理
普通题答对,且没有当前错题状态 不凭空新增错题记录 符合年级规则时请求奖励 2 分,实际奖励还受查重和每日额度限制。
普通题答错 新建或更新为 IN_BOOK 不奖励积分,返回加入错题本的提示。
当前错题复习 答对进入 READY_TO_REMOVE;答错回到或保持 IN_BOOK 答对也不奖励积分,复习目标是巩固并确认掌握。

"答对"和"拿到积分"是两个结果。correct 表示判分结果,awardedPoints 表示实际奖励;达到每日上限或该题已处理过奖励时,答对也可能获得 0 分。

5. 核心规则为什么这样写

问题 结合真实代码理解
为什么判分单独放在工具类? Controller 负责业务流程,ObjectiveJudge 专注答案比较。单选忽略大小写;多选集合必须一致且拒绝重复项;判断题按文本匹配;填空命中任一可接受答案。不实现语义判断或部分给分。
为什么同时保存练习记录和错题状态? practice_record 记录"每次做了什么";wrong_question_state 记录"这道题目前是否需要复习"。历史行为与当前状态用途不同。
为什么答对错题后不立即移出? recordPracticeResult() 先设为 READY_TO_REMOVE,markMastered() 再通过条件更新确认掌握。直接调用移出接口而未处于该状态,会被拒绝。再次答错仍会进入 IN_BOOK。
为什么查询错题前同步历史? syncMissingWrongQuestions() 合并练习、已批改考试和已提交竞赛的错误记录,补建缺失状态。使用 UNION ALL 保留次数,用 INSERT IGNORE 避免覆盖已有复习成果。这些查询入口可能执行补建写入,并非完全只读。
为什么反复练习不会反复得分? 练习奖励调用的来源是 PRACTICE + 题目ID,再加当前学生 ID 查重,不是用每次新建的练习记录 ID。练习可以多次记录,奖励来源只处理一次。
为什么提交练习有事务? submitPractice() 用 @Transactional 包住练习记录、错题更新和积分处理,避免一部分成功、一部分失败;PointService 自身没有事务注解,依赖这层调用提供事务范围。
为什么发积分先锁用户? SELECT ... FOR UPDATE 锁定学生用户行,使同一学生的发分流程在事务内协调执行,保护查重和每日额度计算。每日 120 分是 PRACTICE、EXAM、CONTEST 正向奖励共享的上限。

积分余额来自全部正负流水求和;每日额度只统计当天正向学习奖励。即使实际奖励为 0,PointService 也会写来源流水,重复请求不会在次日自动补发这次奖励。

6. 一次实际阅读怎么走

  1. 在 studentDetail() 中找到 CurrentUser.student(),连接已经学过的认证流程。
  2. 跳到 ensureStudentPracticeAccess(),看清普通练习与错题复习的权限差异。
  3. 回到 submitPractice(),追踪 question → score → correct → wrongBookUpdate → awardedPoints。
  4. 打开 ObjectiveJudge.score() 和 isCorrect(),用"A,C"和"C,A"理解多选比较。
  5. 打开 WrongBookService.recordPracticeResult()、recordWrong()、markMastered(),理解三种状态。
  6. 最后读 PointService.awardLearningPoints(),区分"题目分数""奖励积分""积分余额"。

本次没有核对建表脚本和前端,所以数据库唯一约束的具体定义、页面按钮如何调用这些接口,未在本次阅读文件中确认。掌握这条链路后,再读 PaperController → ExamController → ExamDeadline,就能进一步理解整场考试。

7. 答辩可能怎么问,怎么答

老师可能问 可以怎么答
为什么已经登录,还调用 student()? 登录只确认身份,student() 进一步确认角色,教师和管理员不能调用学生专属练习入口。
系统怎么自动判题? 用 ObjectiveJudge 根据题型比较标准答案与学生答案,正确给满分、错误给零分,不是人工智能语义批改。
为什么错题和练习记录分表? 练习表保留每次作答历史,错题状态表表示当前复习进度,分别解决追溯和管理问题。
学生一直重复答对能刷积分吗? 奖励按学生、PRACTICE 类型和题目 ID 查重,并受每日学习奖励上限限制;练习记录仍可以继续产生。
错题复习答对有积分吗? 当前实现没有。答对会进入待移出状态,学生确认后标记为已掌握。
为什么既需要事务又需要行锁? 事务保证多表修改一起提交或回滚;行锁协调并发请求。它们解决不同问题,积分用户锁需要在事务期间保持有效。
相关推荐
l1t2 小时前
几种数据库软件count(distinct)和count()from group by子查询性能测试
开发语言·数据库
熊猫钓鱼>_>2 小时前
大学图论第 6 讲:网络流与二分图匹配——对偶性让瓶颈自己现形
开发语言·算法·面试·职场和发展·图论·校招·面经
方便面不加香菜2 小时前
C++ 关于多态的知识点讲解
开发语言·c++
jimy12 小时前
C++ 类成员函数的内联定义(inline)详解
开发语言·c++
Nebula_g2 小时前
JavaSE高级:动态代理
java·开发语言·算法·javase
垆边人似月.3 小时前
华为题:盛最多水的容器
数据结构·算法
HPtiger3 小时前
数据结构——插入排序
数据结构·排序算法
青春逝如流水3 小时前
Java 线程与操作系统、硬件的关系:状态、映射与差异
java·开发语言·多线程·ai文章
谢亮_vipxieliang3 小时前
Go gRPC基础与实战
开发语言·后端·golang