文章目录
- 后端类职责说明
-
- 一、整体结构与阅读顺序
- [二、每个 Java 类的职责](#二、每个 Java 类的职责)
-
- [1. 启动与配置:2 个类](#1. 启动与配置:2 个类)
- [2. 登录注册与身份认证:5 个类](#2. 登录注册与身份认证:5 个类)
- [3. 通用响应、异常和规则:7 个类](#3. 通用响应、异常和规则:7 个类)
- [4. 管理员用户管理与个人中心:2 个类](#4. 管理员用户管理与个人中心:2 个类)
- [5. 题库、练习与错题本:2 个类](#5. 题库、练习与错题本:2 个类)
- [6. 试卷管理、在线考试与成绩:3 个类](#6. 试卷管理、在线考试与成绩:3 个类)
- [7. 竞赛中心:1 个类](#7. 竞赛中心:1 个类)
- [8. 积分与奖品商城:2 个类](#8. 积分与奖品商城:2 个类)
- [9. 工作台与学情分析:3 个类](#9. 工作台与学情分析:3 个类)
- [三、内部 record 对象是做什么的](#三、内部 record 对象是做什么的)
- 四、核心业务链路
-
- [1. 登录与鉴权](#1. 登录与鉴权)
- [2. 学生考试](#2. 学生考试)
- [3. 学生竞赛](#3. 学生竞赛)
- [4. 错题复习](#4. 错题复习)
- [5. 积分兑换](#5. 积分兑换)
- [6. 管理员维护用户](#6. 管理员维护用户)
- 五、主要数据库表与代码对应关系
- 六、答辩可讲的重点与难点
后端类职责说明
本文依据 primary-school-backend/src/main/java/com/primary 的现有实现编写,覆盖全部 27 个 Java 主源码文件,并补充内部请求对象、核心业务流程和毕业设计答辩重点。
一、整体结构与阅读顺序
系统面向初中七、八、九年级,包含管理员、教师、学生三类角色。后端使用 Spring Boot 提供 HTTP 接口,以 JdbcTemplate 访问 MySQL,以 R<T> 返回统一的 code、msg、data 结构。
当前项目实际采用"Controller + JdbcTemplate + 部分公共 Service + 工具类"的结构。多数 Controller 同时处理接口、业务校验和 SQL;积分、错题本和 JWT 的通用逻辑已分别抽到 Service。当前主源码中没有独立的 Entity、Mapper 或 Repository 层,不能在论文中把尚未实现的完整三层结构描述为已有设计。
| 类型 | 当前承担的职责 | 代表类 |
|---|---|---|
| 启动类 | 创建 Spring 容器、启动 Web 服务、扫描组件 | PrimarySchoolApplication |
| Controller | 定义接口、接收参数、检查业务权限、组织业务流程和返回数据;多数类还直接执行 SQL | AuthController、ExamController 等 |
| Service | 复用跨模块业务规则或认证能力 | PointService、WrongBookService、JwtService |
| 认证支撑 | 解析请求身份、读取当前用户、进行角色校验 | AuthFilter、CurrentUser、LoginUser |
| 工具与异常 | 共享权限、时间、判分、年级规则,统一错误响应 | ApiSupport、ExamDeadline、ObjectiveJudge、GradeRules 等 |
| 配置类 | 注册密码组件、跨域规则、头像资源映射及启动初始化 | AppConfig |
| 内部 record | 表达请求、计算结果或 SQL 参数片段,不是持久化实体 | 各 Controller 内部请求对象、AwardResult 等 |
建议阅读顺序:启动和配置 → 认证 → 公共响应及权限 → 题库 → 试卷和考试 → 竞赛 → 错题与积分 → 奖品 → 学情统计。
二、每个 Java 类的职责
以下路径均相对于 primary-school-backend/src/main/java/com/primary。
1. 启动与配置:2 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
PrimarySchoolApplication:PrimarySchoolApplication.java |
项目启动入口,执行 SpringApplication.run |
@SpringBootApplication 默认从 com.primary 向下扫描,发现控制器、服务、配置和过滤器 |
AppConfig:config/AppConfig.java |
注册 BCrypt 编码器,配置跨域和头像静态目录,空用户表时补建基础演示账号 | CommandLineRunner 只初始化基础账号及班级关系,不负责建表或完整演示数据;/uploads/** 映射到运行目录的 uploads 文件夹 |
跨域解决浏览器是否允许读取 API 响应;接口权限仍由认证和业务校验决定。启动初始化中的异常会被捕获,应用启动成功不等于数据库已正确初始化。
2. 登录注册与身份认证:5 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
AuthController:auth/AuthController.java |
登录、学生公开注册、算术验证码、当前用户查询和退出登录 | 查询 sys_user、学生及教师班级关系;登录使用 BCrypt 校验并调用 JwtService;注册固定创建 STUDENT,并在事务中保存学生班级 |
AuthFilter:auth/AuthFilter.java |
在控制器之前处理 Bearer Token,恢复本次请求身份 | 调用 JwtService.parse 验签,再按用户 ID 回查启用账号,将实时身份写入请求属性;失效或禁用账号不恢复身份 |
JwtService:auth/JwtService.java |
生成、验证 HS256 JWT | 使用服务端密钥签名,验证格式、签名和有效期;JWT 编码不保密,不能存放密码;载荷中的角色不作为最终实时权限依据 |
CurrentUser:auth/CurrentUser.java |
获取当前请求用户,提供管理员、教师和学生角色入口校验 | 从 RequestContextHolder 的请求属性读取身份;teacher() 允许管理员,具体学科与班级权限还需业务层继续检查 |
LoginUser:auth/LoginUser.java |
以不可变 record 表达当前用户基本身份 | 包含 ID、用户名、姓名、角色、年级、任教学科,提供 isAdmin/isTeacher/isStudent;不包含密码和联系方式 |
主要入口:POST /api/auth/login、POST /api/auth/register、GET /api/auth/captcha、GET /api/auth/me。
验证码保存在内存,使用 ConcurrentHashMap.remove 原子消费,5 分钟有效且只能使用一次。即使后续注册因用户名重复而失败,数据库回滚也不会恢复内存验证码。该机制适用于当前单进程部署,不能描述为跨实例共享验证码。
退出登录接口不在服务端建立 JWT 黑名单,当前流程由前端清除本地令牌。账号禁用或删除会通过每次请求回查即时影响后续访问。
3. 通用响应、异常和规则:7 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
R<T>:common/R.java |
统一成功和失败响应,包含 code、msg、data |
ok 返回业务码 200,fail/error 返回失败信息;JSON 业务码与 HTTP 状态码是不同概念 |
BizException:common/BizException.java |
表达可预期的参数、权限或状态错误 | 携带业务错误码和用户可读消息,继承运行异常,可触发 Spring 默认事务回滚 |
GlobalExceptionHandler:common/GlobalExceptionHandler.java |
将业务异常、参数绑定异常、数据库约束异常和未知异常转换为统一响应 | 查找 JSON 反序列化异常的 cause 链,保留 record 构造器的业务提示;未知异常记录日志,不向客户端返回堆栈 |
ApiSupport:common/ApiSupport.java |
共享 JDBC 行查询、字段转换、创建者权限及学科/年级/班级校验 | 教师权限匹配"年级+班级"组合;学生班级从关系表实时读取;one 取第一行,查询唯一性由主键、唯一键及调用方保证 |
ObjectiveJudge:common/ObjectiveJudge.java |
统一练习、考试和竞赛的客观题判分 | 单选忽略大小写,多选集合完全一致且拒绝重复项,判断按文本匹配,填空命中任一可接受答案;正确满分、错误零分 |
GradeRules:common/GradeRules.java |
集中维护年级顺序、下一年级及练习积分范围 | 当前年级及下一年级属于可积分范围;九年级没有下一年级;实际发分还需检查首次答对、错题模式及每日额度 |
ExamDeadline:exam/ExamDeadline.java |
计算学生个人考试截止时间 | 返回 min(started_at + duration_minutes, end_time);包级工具,由 ExamController 统一调用,无数据库读写 |
ExamDeadline 位于考试包,但属于纯计算工具。本节归入共享机制说明,后续考试模块不重复计数。
4. 管理员用户管理与个人中心:2 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
UserController:user/UserController.java |
管理员创建、查询、修改账号和删除非管理员用户 | 使用 sys_user、student_class_assignment、teacher_class_assignment;创建和更新在事务中完成主表及班级关系,修改角色时清除旧关系再重建 |
ProfileController:user/ProfileController.java |
当前用户查询和维护个人资料、修改用户名或密码、上传头像 | 用户 ID 取自登录身份;姓名与身份信息由管理员维护;修改用户名仅传新用户名,修改密码仍校验原密码;头像文件存磁盘、URL 存数据库 |
主要入口:/api/admin/users、/api/profile、PUT /api/profile/username、PUT /api/profile/password、POST /api/profile/avatar。
修改用户名后用户 ID 不变,JWT 仍通过 ID 查到账号,后续请求回查新用户名。唯一用户名既有应用查重,也有数据库唯一约束保护并发写入。
头像上传检查大小、声明 MIME 类型、扩展名和最终路径,没有进行图片解码内容鉴别。磁盘写文件与数据库更新也不是同一个可自动回滚的事务。
5. 题库、练习与错题本:2 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
QuestionController:question/QuestionController.java |
教师题库筛选、分页、随机抽题、录题、修改、删除和审核;学生练习、查询最近作答和错题入口 | 使用 quiz_question、practice_record,复用 ObjectiveJudge、PointService、WrongBookService;批量录题最多 20 道且整批回滚 |
WrongBookService:question/WrongBookService.java |
维护错题状态,补齐历史错题,提供当前错题和已掌握历史 | 使用 wrong_question_state;答错进入 IN_BOOK,复习答对进入 READY_TO_REMOVE,学生确认进入 MASTERED;再次答错可重新进入错题本 |
主要入口:/api/teacher/questions、/api/student/questions、POST /api/student/questions/{id}/practice、/api/student/wrong-book。
随机抽题的比例模式使用最大余数法分配整数题数,再按难度抽取,题量不足时从其他难度补齐。因此返回的实际难度比例可能不同于目标比例,接口会给出相应提示。
普通自主练习只开放公开、审核通过、用途为 PRACTICE 的当前或下一年级题。当前错题允许继续复习,即使题目不再满足普通练习列表条件;错题复习不奖励积分。
历史错题同步使用三类来源的 UNION ALL,按题目统计后 INSERT IGNORE 补建缺失状态。它不会每次打开错题本都重复累计旧错误,也不会用历史数据覆盖已掌握状态。
6. 试卷管理、在线考试与成绩:3 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
PaperController:paper/PaperController.java |
教师创建和修改试卷,维护题目分值、目标班级、开放时间,发布或取消发布,删除试卷 | 使用 quiz_paper、quiz_paper_question、quiz_paper_target_class;新建先保存 DRAFT,发布前要求题目公开且审核通过;已开始的发布试卷禁止修改 |
ExamController:exam/ExamController.java |
学生考试列表、开始/恢复、答案保存、提交判分、成绩和逐题详情 | 使用 quiz_record、quiz_answer;保存和交卷先锁答卷;使用 ExamDeadline;交卷调用判分、错题和积分服务,直接完成客观题 GRADED 状态 |
GradingController:grading/GradingController.java |
教师和管理员读取考试成绩及答题明细 | 列表按已完成状态查询,普通教师限制任教学科及试卷创建人;详情再检查学生班级范围;当前没有人工修改分数接口 |
主要入口:/api/teacher/papers、/api/student/exams、/api/student/records/{recordId}、POST /api/student/records/{recordId}/submit、/api/teacher/grading。
试卷内题目分值来自 quiz_paper_question.score,不应混用题库中的默认 quiz_question.score。一张题库题目可以在不同试卷中设置不同分值。
考试详情新增的 effectiveEndTime 表示个人有效截止;startTime/endTime 仍表示试卷开放窗口。标准答案与解析只在 SUBMITTED/GRADED 状态返回,进行中的答卷在 SQL 层置空答案字段。
7. 竞赛中心:1 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
ContestController:contest/ContestController.java |
学生查竞赛、报名、开始、保存、提交、查榜;教师维护竞赛、目标班级、题目和查看结果 | 使用竞赛主表及五张关系/记录表;报名前检查发布和开始时间;开始要求已报名;交卷锁答卷,客观题自动判分后写错题和积分 |
主要入口:/api/contests、POST /api/contests/{id}/register、POST /api/contests/{id}/start、PUT /api/contests/{id}/answers、POST /api/contests/{id}/submit、GET /api/contests/{id}/ranking、/api/teacher/contests。
竞赛报名记录表示资格,答题记录表示实际参与,不能用报名人数代替完成参赛人数。已有学生报名后禁止改变面向年级或班级;竞赛已开始后禁止编辑。
当前竞赛使用全局 start_time/end_time 控制答题窗口,没有套用考试的个人 effectiveEndTime。duration_minutes 在请求缺省结束时间时参与生成 end_time,不能把它描述为当前竞赛已实现的个人倒计时上限。
排行榜在竞赛结束且学生已提交后可查看,依次按总分降序、用时升序、提交时间升序、记录 ID 升序排序。ROW_NUMBER 产生唯一连续名次,班级筛选后重新计算筛选范围内排名。
8. 积分与奖品商城:2 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
PointService:point/PointService.java |
计算积分余额、当天剩余额度,按业务来源幂等发放学习积分 | 使用 student_point_log;余额为全部正负流水之和;学习奖励每日最多 120 分;锁学生用户行后查重、计算额度并写流水 |
RewardController:reward/RewardController.java |
学生浏览奖品及申请兑换,教师维护奖品和处理兑换,管理员审核奖品 | 使用 point_reward、point_redeem_record、student_point_log;申请不预扣资源,通过兑换审核时锁记录并扣库存、写负积分流水;创建者每月预算 500 分 |
主要入口:/api/student/rewards、/api/teacher/rewards、/api/admin/rewards/{id}/approve、/api/teacher/redeem-records。
积分来源主键有明确约定:PRACTICE 使用题目 ID,EXAM 使用答卷 ID,CONTEST 使用竞赛 ID,REDEEM 使用兑换申请 ID。来源编号需要与学生 ID、来源类型一起判断,不能独立解释。
PointService.awardLearningPoints 自身没有 @Transactional,由练习、考试、竞赛事务接口调用。用户行锁依赖外层事务覆盖"查重、额度计算、插流水"全过程。实际奖励为零也记录流水,防止同一来源在次日重复领取。
商城有两个不同状态维度:奖品的 approval_status 表示管理员内容审核,奖品的 status 表示上下架;兑换申请的 status 表示教师处理学生申请的结果。二者不能混为同一种审核。
9. 工作台与学情分析:3 个类
| 类及路径 | 作用 | 关键机制与协作 |
|---|---|---|
DashboardController:dashboard/DashboardController.java |
按角色返回首页数量、趋势、学生积分、近期成绩、竞赛及待办 | 管理员全量统计,教师按本人和学科统计,学生按本人统计;待办按考试、竞赛、练习顺序最多返回 6 条,路径沿用已有前端路由 |
StudentAnalysisController:analysis/StudentAnalysisController.java |
学生作答概览、分学科与题型正确率、最近六次考试趋势 | 三类学习来源使用 UNION ALL 合并;固定分类左连接补零;作答次数不是去重题数;趋势先取最近六条再正序显示 |
TeacherAnalysisController:analysis/TeacherAnalysisController.java |
普通教师任教班级概览、知识点正确率和班级学生排名 | allowed CTE 固定任教范围,学科参数限制教学内容;合并考试和竞赛为百分制得分率;不包含普通练习;管理员不进入此个人任教统计接口 |
主要入口:/api/dashboard/admin、/api/dashboard/teacher、/api/dashboard/student、/api/student/analysis、/api/teacher/analysis。
教师班级均分先算每个学生的平均得分率,再按班级平均,每位参测学生权重一致;及格与优秀分别以 60、85 为阈值。统计依据学生当前班级,未建立历史班级快照,不能描述为按参加测评时的历史班级统计。
学生综合正确率按"正确作答次数 / 总作答次数"计算。教师班级均分、学生正确率和考试得分率是不同口径,论文应分别说明。
以上主源码计数:2 + 5 + 7 + 2 + 2 + 3 + 1 + 2 + 3 = 27,其中 ExamDeadline 只计一次。
三、内部 record 对象是做什么的
这些类型定义在所属 Java 文件内,不对应数据库表,主要用于 JSON 参数绑定、结果表达和字段整理。
| 所属类 | 内部对象 | 用途 |
|---|---|---|
AuthController |
LoginRequest、RegisterRequest |
登录和学生注册参数,构造时完成必填及角色等基础校验 |
AuthController |
CaptchaItem |
保存服务端验证码正确答案和过期时间,不返回给前端 |
UserController |
UserRequest、UpdateUserRequest |
管理员新建和修改账号的参数;修改请求不包含密码 |
UserController |
TeachingAssignment、UserFields |
表达任教班级以及按角色归一化后的账号字段 |
ProfileController |
ProfileRequest、UsernameRequest、PasswordRequest |
联系资料、新用户名、旧密码与新密码确认参数 |
QuestionController |
PracticeRequest、BatchQuestionRequest、QuestionRequest |
自主练习答案、批量录题列表、单题定义 |
QuestionController |
QueryParts |
将动态 SQL 片段与按顺序绑定的参数放在一起,供多个查询复用 |
PaperController |
PaperRequest、QuestionItem |
试卷配置及题目 ID、顺序、试卷内分值 |
ExamController |
AnswerRequest、AnswerItem |
批量考试答案及单题编号、答案文本 |
ContestController |
AnswerRequest、AnswerItem、QuestionItem、ContestRequest |
竞赛答案、组题与竞赛基本配置;与考试请求名称相似但分别属于不同类 |
PointService |
AwardResult |
实际发放分数、是否重复、总余额及每日剩余额度 |
WrongBookService |
WrongBookUpdate |
错题状态、练习次数、错误次数、连续答对次数及是否可以移出 |
RewardController |
RewardRequest、ReviewRequest |
奖品配置及可选审核备注 |
record 的紧凑构造器在 JSON 反序列化时执行。构造器负责默认值及不依赖数据库的基础校验,数据库权限、唯一性和状态校验仍由业务方法执行。
四、核心业务链路
1. 登录与鉴权
- 前端提交用户名、密码,
AuthController查询启用账号并通过 BCrypt 验证。 JwtService按用户 ID 等字段生成有时限的签名令牌,接口返回令牌和脱敏资料。- 后续请求携带
Authorization: Bearer ...,AuthFilter验证令牌后回查账号实时状态。 CurrentUser读取本次请求身份并校验角色;各业务接口再校验数据创建者、任教学科或班级范围。- 业务成功返回
R.ok;失败抛出BizException或由全局处理器转换统一失败响应。
2. 学生考试
- 教师保存草稿试卷,配置题目、试卷内分值、目标班级和开放窗口,发布前校验题目审核状态。
- 学生在本人年级班级范围内查看考试,在开放窗口内调用开始接口。
- 首次创建 IN_PROGRESS 答卷并保存
started_at,重复进入复用唯一答卷和原开始时间。 - 详情返回
effectiveEndTime = min(started_at + duration_minutes, end_time),刷新页面不重新计时。 - 自动保存先锁答卷,再检查状态、个人截止时间及题目归属,使用唯一键新增或覆盖答案。
- 手动或超时提交先锁答卷;截止前接受请求中的答案,截止后仅结算服务端已保存答案。
- 补齐未答题空记录,按
ObjectiveJudge判分,写错题,更新总分和 GRADED 状态。 - 以答卷 ID 为积分来源发奖,及格得 10 分、否则 5 分,实际发放受每日额度约束。
- 事务整体提交后返回成绩;重复交卷读取既有结果,不再次判分或发奖。
3. 学生竞赛
- 教师配置竞赛、目标班级和题目,发布时校验题目公开且审核通过。
- 学生必须在竞赛开始前报名,报名唯一约束防止重复报名。
- 开始后、结束前且已报名的学生可创建或恢复答卷,答卷保留实际开始时间。
- 在竞赛全局窗口内保存答案,交卷锁定记录并按是否过截止决定是否接受新答案。
- 判分、写错题、计算有效用时、更新 SUBMITTED 状态和积分流水在同一事务中完成。
- 有得分请求奖励 20 分,零分请求奖励参与分 10 分,实际发放仍受每日上限约束。
- 竞赛结束后,已提交学生按成绩与用时等条件查看排行榜。
4. 错题复习
- 普通练习、考试或竞赛判错时记录错题,重复答错累加次数并保持或重新进入 IN_BOOK。
- 查询错题本时补齐缺失的历史错题状态,保留已有复习成果。
- 当前错题复习答对后进入 READY_TO_REMOVE,界面可以展示"可移出"状态。
- 学生确认移出后更新为 MASTERED,历史错误与练习记录继续保留。
- 错题复习不奖励积分;再次答错时重新进入 IN_BOOK。
5. 积分兑换
- 教师创建奖品后进入待管理员审核且下架状态,管理员通过后有库存才上架。
- 学生浏览可用奖品,接口附带余额差额和创建者预算信息。
- 学生申请兑换时检查条件并保存 PENDING,不立即扣积分或库存,也不预占资源。
- 教师处理本人奖品申请,管理员可处理全部;先锁申请,确认仍处于待审核。
- 同意时锁学生、奖品创建者和奖品,重新验证余额、月预算和库存。
- 扣库存、必要时下架、写 REDEEM 负积分流水、更新 APPROVED 及审核信息,事务共同提交。
- 拒绝时只更新 REJECTED 与审核备注,无需退分或回补库存,因为申请阶段没有扣减。
6. 管理员维护用户
- 管理员接口首先校验 ADMIN 身份,公共学生注册不能创建教师或管理员。
- 创建用户时加密初始密码,按角色保存学生班级或教师任教关系。
- 修改角色或班级时更新主表、删除旧关系、重建新关系,事务保证三者一致。
- 禁用或删除账号后,认证过滤器回查时不再恢复其身份。
- 所有 ADMIN 账号均禁止通过当前删除接口删除,防止丢失管理入口。
五、主要数据库表与代码对应关系
| 业务 | 表 | 主要读写类 |
|---|---|---|
| 用户与班级 | sys_user、student_class_assignment、teacher_class_assignment |
AuthController、AuthFilter、UserController、ProfileController、ApiSupport |
| 题库 | quiz_question |
QuestionController,试卷、竞赛、错题和统计模块也读取 |
| 试卷配置 | quiz_paper、quiz_paper_target_class、quiz_paper_question |
PaperController,ExamController 和统计模块读取 |
| 考试作答 | quiz_record、quiz_answer |
ExamController,GradingController、错题及统计模块读取 |
| 普通练习 | practice_record |
QuestionController,错题与统计模块读取 |
| 错题状态 | wrong_question_state |
WrongBookService |
| 竞赛配置 | contest、contest_target_class、contest_question |
ContestController,首页和统计模块读取 |
| 竞赛参与 | contest_registration、contest_record、contest_answer |
ContestController,错题与统计模块读取 |
| 积分流水 | student_point_log |
PointService、RewardController,首页读取 |
| 奖品与兑换 | point_reward、point_redeem_record |
RewardController,首页读取审核数量 |
表组覆盖 20 张业务表。年级与班级组成业务范围,关系表保存的是年级、班级名称及用户或测评主键,不应描述为另有独立班级实体表。
六、答辩可讲的重点与难点
| 重点 | 清晰解释 | 代码位置 |
|---|---|---|
| 实时身份与角色权限 | JWT 验证身份标识,数据库回查保证禁用、改名和角色变更及时生效;角色进入权限与数据范围权限分开检查 | JwtService、AuthFilter、CurrentUser、ApiSupport |
| 密码存储 | BCrypt 保存不可逆哈希,校验通过 matches;随机盐使同一明文的不同编码结果不必相同 | AuthController、ProfileController、AppConfig |
| 个人考试计时 | 开放窗口允许学生进入的时间与每位学生答题时长不同,取两个截止时间的较早者并保留原始开始时间 | ExamDeadline、ExamController |
| 幂等提交 | 锁定答卷后检查完成状态,重复请求返回既有结果,避免二次判分、错题计数和积分 | ExamController.submit、ContestController.submit |
| 多表事务 | 判分、错题、成绩和积分一起成功或回滚;兑换的库存、扣分和状态也一起提交 | 各核心接口的 @Transactional |
| 行锁与唯一约束 | 行锁协调并发的读后写流程,唯一键防止重复记录,事务覆盖锁的有效期;单有注解不等于天然消除所有竞争 | 答卷读取、积分用户锁、奖品审核、答案 UPSERT |
| 自增主键取得 | last_insert_id() 是连接级状态,需要插入和主键查询复用连接,不能用全表最大 ID 代替 |
注册、用户创建、组卷、竞赛创建和录题 |
| 错题状态 | 待复习、答对待移出、已掌握是明确状态;移出保留历史,再次答错可重新进入 | WrongBookService |
| 统一判分 | 三个业务入口复用一套规则;多选忽略顺序却拒绝重复,填空只匹配可接受答案,不实现语义判分 | ObjectiveJudge |
| 学习积分防重复 | 来源类型和来源 ID 定义清晰,用户锁保护每日额度,零分流水也表示该来源已经处理 | PointService |
| 两阶段兑换 | 申请不占资源,通过时再次检查并扣资源,拒绝只更新状态 | RewardController |
| 统计口径与 SQL | 保留重复作答用 UNION ALL,补零用固定分类加 LEFT JOIN,防除零用 NULLIF,排名用窗口函数 | 两个 AnalysisController、DashboardController |
@Transactional 默认在经 Spring 代理的外部方法调用中生效。当前事务接口内部调用辅助方法会沿用已有事务,不应解释为每个内部方法自动创建独立事务。默认对运行异常回滚,BizException 符合该规则;文件系统和内存验证码不受数据库事务自动回滚控制。