news 2026/10/22 6:29:08

Spring Boot在线考试系统毕业论文:核心设计、随机组卷与防作弊实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot在线考试系统毕业论文:核心设计、随机组卷与防作弊实践

简介:基于Spring Boot的在线考试系统毕业设计论文,以单个docx文档打包,压缩包约2.9MB,已有112人学习。这份论文面向计算机相关专业学生及毕业设计开发者,围绕在线考试系统的设计与实现展开,涵盖研究背景、研究目的与意义、国内外研究现状、主要研究内容,并详细呈现了绪论、相关技术概述、系统设计与实现等核心章节。文档系统介绍了Spring Boot框架、Java语言和前端HTML/CSS/JavaScript等技术在系统中的应用,并详细设计了学生注册登录、查看考试信息、编辑个人信息,以及教师管理试题库、创建在线考试、批改考试和编辑个人信息等功能模块。通过阅读该论文,读者可以完整掌握在线考试系统的需求分析、技术选型、模块划分与实现要点,并了解系统部署和测试的基本方法,为毕业论文撰写、毕业设计开发或同类课题研究提供直接参考。

1. Spring Boot在线考试系统毕业论文:为什么“先写论文目录再做系统”才不慌

打开“springboot在线考试系统毕业论文.docx”之前,大多数人的打算是:先把后台管理的页面铺满,再把考试流程接上,最后回填论文。真开始做才发现,在线考试系统的难点根本不在功能数量,而在时间、异常和并发这三条硬约束:考试开始前能不能进、结束后还在作答怎么办、中途刷新页面答案丢不丢、交卷瞬间成绩会不会重复。这些约束恰好是论文里“系统设计”和“系统测试”两章该写、却最容易空掉的地方。

我见过不少做这个题目的同学,系统跑了大半年,论文却憋不出几页,核心原因就是把顺序搞反了。正确做法是先列论文目录,用目录倒推系统需要哪些模块、哪些模块值得写代码去支撑,然后再动手。这篇内容就按这个路径来:先讲论文章节与系统设计的对应关系,再给出一套能落地的 Spring Boot 主流程,最后集中写开发过程里的避坑记录。

适合读这篇的,是正在选毕业设计方向,或者已经选了在线考试系统但不知道时间该花在哪里的同学。全文不打算把每个管理页面都讲一遍,只把最能支撑论文的主链路和三个高价值功能点讲透。

2. 先倒推毕业论文结构再定系统:章节骨架、功能边界与技术选型

不要养成“先把系统做完再写论文”的习惯。系统做完时,你的时间往往不够了,论文里最需要的需求分析素材、数据库设计依据、测试数据全都没留。我一般会先用一下午把论文目录列出来,再决定系统做什么、不做什么。

2.1 用论文章节反向推导开发里程碑

一篇本科毕业论文的通用结构是:绪论、需求分析、系统设计、系统实现、系统测试、总结。把开发工作挂到每一章下面,你会发现真正决定论文质量的不是“写”而是“素材采集”。比如“需求分析”这章,不是靠抄模板写出来的,而是靠你在开工前画的角色用例和业务链路。

论文一级章节这一章要回答的问题对应开发工作
绪论为什么做在线考试、比传统考试强在哪定差异点:随机组卷、防作弊、自动评分
需求分析给谁用、每个角色的核心操作是什么画角色用例、梳理考试主链路
系统设计架构怎么分层、数据库怎么建、核心流程怎么走数据库DDL、接口清单、时序图素材
系统实现关键功能怎么落地、边界条件怎么处理核心代码、功能截图、接口说明
系统测试用数据证明系统可用测试用例、结果记录

按这个表倒推开发里程碑,顺序会很清晰:

  • 第一周:完成需求分析文档和角色用例,同时画出数据库 E-R 图草稿。
  • 第二周:把 DDL 写出来,接口清单列出来。
  • 第三到第五周:写后端接口和页面,边写边截关键功能的图。
  • 第六周:集中整理测试记录,回填论文章节。

这个顺序反直觉,但对论文交付效率最高。先写代码再补需求分析,大概率会漏掉一两张表,后面改起来伤筋动骨。

2.2 功能边界怎么划:三个角色、一条考试链路、三个“不做”

在线考试系统的业务主链路非常固定:教师建题库,创建一次考试并从题库选题,发布考试;学生在开始时间和结束时间之间登录系统,点击开始考试,系统按试卷出题;学生作答过程中自动保存答案,提交或到时自动收卷;客观题自动评分,简答题人工评分;学生查看成绩与答案解析。

围绕这条链路,角色功能可以这样拆:

  • 学生:注册登录、查看可参加的考试、开始考试、作答、提交、查看成绩与错题解析。
  • 教师:维护题库、创建试卷、发布或截止考试、人工评阅简答题。
  • 管理员:维护用户账号、维护科目分类等基础数据。

更重要的是划清“三个不做”。我在做类似项目时通常会明确放弃摄像头监考、批量导入 Excel、成绩统计可视化大屏这三块。摄像头监考涉及音视频流处理和前端权限,论文阶段根本做不深;批量导入需要兼容各种格式的脏数据,工作量不小但技术含量低;统计大屏虽然好看,但属于锦上添花,做完后面试官盯着问你数据刷新机制反而难收场。在需求分析里写“这三个方向作为后续工作”,比做成半成品体面得多。

2.3 技术选型怎么选、论文里怎么解释

这个题目选技术栈几乎没有悬念:Spring Boot 做后端,MyBatis-Plus 操作数据库,MySQL 存业务数据,Redis 处理答题缓存和考试状态,前端用 Thymeleaf 还是 Vue 取决于你的时间。论文里不需要长篇大论,但要给出一句话理由。

技术组件论文里常见的选型表述
Spring Boot简化了 Spring 的配置和部署,自带容器,适合快速构建独立服务
MyBatis-Plus基础 CRUD 不需要重复编写,条件构造器覆盖了本系统的多数查询场景
MySQL考试业务是强数据一致性场景,关系型数据库的事务机制比 NoSQL 更可靠
Redis保存答题过程中的增量数据,记录心跳时间,控制同一账号的并发登录
JWT无状态登录,前后端分离时后端不需要维护大量 session

有一个版本参数要注意:如果本机还是 JDK 8,选 Spring Boot 2.7.x 而不是 3.x,因为 3.x 最低要求 JDK 17。这个选择直接卡住你能不能跑起来,论文里写清楚“采用 Spring Boot 2.7 是基于环境 JDK 版本约束”,反而显得你做了权衡。

3. 用 Spring Boot把在线考试主流程跑通:四张核心表与三个关键接口

“系统能跑”和“论文能写”的分界,在于核心表结构有没有经得起推敲。在线考试系统最忌讳的是“一张考试表 + 一张记录表”走天下,那样设计不了防作弊,也支撑不了自动评分。我建议动手写 Service 之前先把四张核心表建好。

3.1 四张核心表:考试、题库、试卷、答题卡

下面的 DDL 可以直接用来建库,字段注释已经写清楚,论文里的 E-R 图也能直接对着画。

CREATE TABLE exam ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '考试ID', title VARCHAR(100) NOT NULL COMMENT '考试名称', total_score DECIMAL(6,1) DEFAULT 100.0 COMMENT '试卷总分', duration_minutes INT NOT NULL COMMENT '考试时长(分钟)', start_time DATETIME NOT NULL COMMENT '考试开始时间', end_time DATETIME NOT NULL COMMENT '考试结束时间', status TINYINT DEFAULT 0 COMMENT '0草稿 1已发布 2已结束', creator_id BIGINT COMMENT '创建教师ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_exam_time (start_time, end_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试主表'; CREATE TABLE question ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '题目ID', type TINYINT NOT NULL COMMENT '0单选 1判断 2多选 3简答', content TEXT NOT NULL COMMENT '题干', options TEXT COMMENT '选项JSON,单选多选使用', answer TEXT COMMENT '标准答案', score DECIMAL(5,1) NOT NULL COMMENT '题目默认分值', difficulty TINYINT DEFAULT 1 COMMENT '难度 1-3', subject_id BIGINT COMMENT '科目分类ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_question_subject (subject_id, type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='题库表'; CREATE TABLE exam_paper ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id BIGINT NOT NULL COMMENT '考试ID', question_id BIGINT NOT NULL COMMENT '题目ID', question_order INT COMMENT '题目在试卷中的顺序', actual_score DECIMAL(5,1) COMMENT '本题在本次考试中的实际分值', INDEX idx_paper_exam (exam_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试卷与题目关联表'; CREATE TABLE exam_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id BIGINT NOT NULL, student_id BIGINT NOT NULL, start_time DATETIME NOT NULL COMMENT '开考时间', submit_time DATETIME NULL COMMENT '提交时间', total_score DECIMAL(6,1) DEFAULT 0 COMMENT '最终得分', status TINYINT DEFAULT 0 COMMENT '0考试中 1已提交 2异常终止', INDEX idx_record_student (student_id, exam_id), UNIQUE KEY uk_record_exam_student (exam_id, student_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试记录表'; CREATE TABLE answer_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, record_id BIGINT NOT NULL COMMENT '考试记录ID', question_id BIGINT NOT NULL, user_answer TEXT COMMENT '学生答案', is_correct TINYINT DEFAULT NULL COMMENT '客观题是否正确 0否 1是', got_score DECIMAL(5,1) DEFAULT 0 COMMENT '本题得分', INDEX idx_detail_record (record_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='答题明细表';

表结构里有一个细节值得注意:exam_paper 里的 actual_score 是这次考试实际生效的分值,而不是直接读 question.score。这样教师后续在题库里修改某道题的默认分值,已经结束的考试成绩不会被连带影响,论文里可以把这点写成“试卷快照机制”。

提示:外键索引在业务程序里校验,不要在数据库层建物理外键。在线考试系统有一定并发,物理外键会在插入时额外检查关联记录,性能不划算。论文画图时画出外键关系即可。

3.2 基于JWT的登录与开考接口:先解决“谁在答题”

登录认证这个环节,很多同学会直接写在每个 Controller 里,每次取用户都要重复解析 token。更清晰的做法是统一拦截器校验,再把用户信息放进 request 属性。

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); // token格式固定为 "Bearer xxxx",避免token拼在URL上被日志带走 if (token == null || !token.startsWith("Bearer ")) { throw new BizException(401, "未登录或登录已过期"); } Claims claims = JwtUtil.parseToken(token.substring(7)); if (claims == null) { throw new BizException(401, "登录已过期,请重新登录"); } request.setAttribute("userId", Long.valueOf(claims.get("userId").toString())); request.setAttribute("role", claims.get("role").toString()); return true; } }

这段代码的逻辑很直白:token 从 Authorization 头里取,统一加 “Bearer ” 前缀是为了和第三方接口对接时保持格式一致。解析一次后放到 request 属性里,后续业务方法直接调用,不用每个方法都重复解析。解析失败统一抛异常,由全局异常处理器转换成 JSON,不让用户看到堆栈信息。

接下来是开考接口的核心逻辑,这里最要紧的是“幂等”,防止双击按钮生成两份答题卡。

public ExamRecord startExam(Long examId, Long userId) { Exam exam = examMapper.selectById(examId); if (exam == null || exam.getStatus() != 1) { throw new BizException("考试不存在或未发布"); } LocalDateTime now = LocalDateTime.now(); // 状态只是人为控制,开始和结束时间才是硬边界 if (now.isBefore(exam.getStartTime())) { throw new BizException("考试尚未开始"); } if (now.isAfter(exam.getEndTime())) { throw new BizException("考试已结束"); } // 重复点击开始考试,直接返回旧记录 ExamRecord old = recordMapper.selectByExamIdAndStudentId(examId, userId); if (old != null) { return old; } // 新建考试记录,并给这套卷子的每道题初始化答题明细 ExamRecord record = new ExamRecord(); record.setExamId(examId); record.setStudentId(userId); record.setStartTime(now); record.setStatus(0); recordMapper.insert(record); List<ExamPaperQuestion> questions = paperMapper.selectQuestionsByExamId(examId); answerDetailMapper.batchCreateBlankAnswer(record.getId(), questions); return record; }

开考接口的入参只有两个:考试 ID 和学生 ID。第一步查考试主表,校验状态和时间,第二步查答题卡是否已存在,第三步才创建记录。注意UNIQUE KEY uk_record_exam_student兜底,即使两个请求同时进来,数据库层也会拦下重复记录。

3.3 答题中途保存与到期自动交卷:把“断网不丢答案”写进论文

没有中途保存的在线考试系统,在论文里是经不起追问的。面试官只要问一句“学生考试时断电,刷新页面答案还在不在”,没有这一段的系统当场露怯。我的做法是:答案先写 Redis,再按条件刷回 MySQL,同时用定时任务处理超时交卷。

// fixedDelay表示上一次任务执行结束后再等30秒跑下一次 @Scheduled(fixedDelay = 30 * 1000) public void autoSubmitOverdueExams() { List<ExamRecord> list = recordMapper.selectUnsubmittedOverdue(LocalDateTime.now()); for (ExamRecord record : list) { doSubmit(record.getId()); } }
public void saveOneAnswer(SaveAnswerDTO dto) { String key = "exam:answers:" + dto.getRecordId(); // 哈希结构里 field是题目ID,value是学生答案JSON redisTemplate.opsForHash().put(key, dto.getQuestionId(), JSON.toJSONString(dto.getAnswer())); Long totalQuestion = paperMapper.countByExamId(dto.getExamId()); Long answered = redisTemplate.opsForHash().size(key); // 已答数量等于卷面题目数,说明这份卷子已经做满,直接提交 if (answered >= totalQuestion) { flushRedisToDb(dto.getRecordId()); doSubmit(dto.getRecordId()); } }

第一段定时任务每 30 秒扫一次超时未交的考试记录,逐条调用 doSubmit,保证学生到点不交卷也不会被无限挂起。第二段是学生每题作答后的保存逻辑,Redis 哈希的 field 用题目 ID,value 是 JSON 序列化后的答案,这样学生答一题就缓存一题。

参数说明:fixedDelay = 30 * 1000 是毫秒,表示任务完成后间隔 30 秒再执行。30 秒的粒度对这个场景够用,不会对数据库产生压力;如果你希望收卷更准,可以缩减到 10 秒,但没必要。flushRedisToDb 是把 Redis 中这份卷子的全部答案批量写入 answer_detail,执行完后调用 doSubmit 进入收卷流程。doSubmit 里更新 exam_record 时带条件where status = 0,影响行数为 0 说明已被其他线程提交,直接返回,保证并发下不会重复算分。

4. 论文里最能撑起工作量的三个功能点:随机组卷、防作弊与成绩计算

第 3 章跑通的是主流程,但论文的“系统设计”和“系统实现”两章,需要至少三个有深度的功能点撑场面。随机组卷、防作弊、成绩计算是这类题目中最稳妥的三个。

4.1 随机组卷:按题型、难度、分值生成,而不是“随机取N道题”

很多新手写随机组卷,就是一句SELECT * FROM question ORDER BY RAND()。这在数据量小的时候能跑,但生成的卷子没有结构:可能全是单选题,可能总分不是 100。正规做法是先定组卷规则,再按规则分组抽题。

public List<Question> pickRandomQuestions(PaperRule rule) { List<Question> bank = questionMapper.selectBySubject(rule.getSubjectId()); // 先按“题型_难度”分组,避免随机后卷面结构失衡 Map<String, List<Question>> grouped = bank.stream() .collect(Collectors.groupingBy(q -> q.getType() + "_" + q.getDifficulty())); List<Question> result = new ArrayList<>(); for (RuleItem item : rule.getItems()) { List<Question> group = grouped.getOrDefault(item.getType() + "_" + item.getDifficulty(), List.of()); Collections.shuffle(group); result.addAll(group.subList(0, Math.min(item.getCount(), group.size()))); } // 抽完后校验总分,防止规则配置错误导致卷面分不等于设定总分 BigDecimal actual = result.stream() .map(Question::getScore) .reduce(BigDecimal.ZERO, BigDecimal::add); if (actual.compareTo(rule.getTotalScore()) != 0) { throw new BizException("题型分值设置与总分不一致"); } return result; }

组卷规则的参数含义是:题目从哪个科目里抽、每个题型和难度组合抽几道、每道题多少分。代码先按题型_难度分组,再用Collections.shuffle打乱后截断,避免循环随机取下标导致同一道题重复入卷。最后一步做总分校验,实际分和设定总分不一致就直接抛异常,提示教师在规则配置页检查分值。

这里有个常见误用一定要避开:有人为了让试卷不重复,直接用当前时间做随机种子,结果同一秒内组出的卷子完全一样。更好的做法是让组卷结果与考试记录绑定,把抽题结果写入 exam_paper 表,一次考试固化为一份试卷快照。

4.2 防作弊与时间控制:心跳和页面切换标记,深度刚好适合论文

在线监考如果要做到“识别学生是否在看手机”,那不是毕业设计能驾驭的,需要人脸识别和动作检测模型,工程量大且容易被追问细节。我一般建议做两个轻量但完整的机制:前端心跳上报和页面切换计数。

// 考试开始后,前端每20秒上报一次心跳,redis过期时间设为2分钟 String key = "exam:heartbeat:" + recordId; redisTemplate.opsForValue().set(key, String.valueOf(studentId), 2, TimeUnit.MINUTES); // 学生发生页面切换时,由前端累加次数并同步到后端标记 if (switchCount >= 3) { examRecordMapper.markAbnormal(recordId); // status=2,标记为异常终止 }

心跳的作用是判断“这个学生是否还停留在考试页面”。如果 Redis 里对应 key 过期,说明学生已经离开考试页面超过 2 分钟,这份记录会被标记为异常。页面切换计数则记录学生切换页面的次数,连续切换超过阈值只做标记,不自动取消成绩,最终是否判作弊由教师在后台人工处理。这个设计在论文里可以写成“系统只保留异常证据,不代替教师做判断”,既体现了边界感,又避免误伤学生。

这里的参数值得在论文里写明:心跳间隔 20 秒,Redis key 过期时间 120 秒。理论依据很简单,心跳间隔必须小于过期时间,否则正常在线学生也会被误判离线。我一般取 4 到 6 倍的余量,20 秒的间隔对页面性能没有影响。

4.3 成绩计算:客观题自动评分、主观题人工评分,总分从明细汇总

成绩计算最容易做乱:有人把总分直接存 exam_record 的一个字段,考完就 update 一次,结果改了一道题的分数后总分就变得对不上。正确做法是,最终得分永远从 answer_detail 逐题汇总,不单独维护一个“总分”字段。

public BigDecimal scoreQuestion(Question q, String studentAnswer) { if (q.getType() == 0 || q.getType() == 1) { // 单选和判断:去掉首尾空格后直接比较 if (studentAnswer == null || studentAnswer.trim().isEmpty()) { return BigDecimal.ZERO; } return studentAnswer.trim().equals(q.getAnswer().trim()) ? q.getScore() : BigDecimal.ZERO; } if (q.getType() == 2) { // 多选:答案转成Set比较,顺序不影响得分 Set<String> std = splitToSet(q.getAnswer()); Set<String> stu = splitToSet(studentAnswer); return std.equals(stu) ? q.getScore() : BigDecimal.ZERO; } // 简答题不自动评分,交给人工评分接口 return BigDecimal.ZERO; }

这段代码的评分逻辑分了三种情况。单选和判断直接做字符串比较,但先 trim 掉空格,避免学生在选择题后面多敲一个空格被误判。多选题不能按字符串顺序比较,因为学生选的顺序可能和标准答案不一致,转成 Set 再比较是常见做法。简答题返回零分,不在这里做语义识别。

public void manualScore(Long answerDetailId, BigDecimal score) { answerDetailMapper.updateScore(answerDetailId, score); // 每次改完一道题的分,重新汇总当前卷总分 BigDecimal total = answerDetailMapper.sumScoreByRecordId(recordId); examRecordMapper.updateTotalScore(recordId, total); }

人工评分接口的思路是:教师输入每题得分,更新 answer_detail,然后重新汇总整张卷子。这样任何一道题分数调整后,总分自动同步,不会出现“明细分加总 80,总分却是 85”的尴尬。

5. Spring Boot在线考试系统避坑记录:五个答辩前才发现的问题

这个题目我前后接触过三版,每次都有新的翻车点。下面这五条是最典型的,前四条是开发问题,最后一条是论文写作问题,都按“现象、原因、解决”的顺序说清楚。

5.1 同一场考试重复点击“开始考试”,出现了两张答题卡

现象:学生端快速双击“开始考试”按钮,考试记录列表里出现两条相同考试记录,第二次进入考试时页面多了一倍的题目。

原因:开考接口没有做幂等校验。两个请求几乎同时到达,后端线程都执行了“查无记录,创建新记录”,于是插入了两条 exam_record。

解决:服务层先按 (examId, studentId) 查重,同时在 exam_record 表加UNIQUE KEY uk_record_exam_student (exam_id, student_id),数据库兜底。代码在 3.2 节已经给出,这两层缺一不可。只靠业务代码查重,还是可能被极端并发击穿。

5.2 学生刷新页面后白屏,Redis中暂存的答案没刷回 MySQL

现象:学生答题途中强刷页面,重新进入考试后之前答的内容全部消失,页面一片空白。

原因:保存逻辑只在 Redis 里写了缓存,没有及时刷回数据库。刷新页面时 Redis key 因为过期被清理,重新进入考试时又从数据库读答案,读到的自然是空的。

解决:把保存策略改成“双写”。每次作答先写 Redis,同时异步批量刷回 answer_detail,不让 Redis 成为唯一副本。我一般让前端每答完一题就触发保存接口,后端保存接口里先把数据写进 Redis,然后立即通过消息队列或异步线程池刷写数据库。这样即使 Redis 重启,答案也不会丢。

5.3 考试时间跨天,前端传了个字符串,后端时区判断错乱

现象:考试设成 00:00 到 23:59,结果凌晨 0 点整点附近,学生端显示“考试未开始”,下午再看时又显示“考试已结束”。

原因:前端用new Date().toLocalTimeString()这类方法转换时间,得到的字符串没有时区信息,后端用不同时区解析后产生了 8 小时偏差。MySQL 连接的 serverTimezone 参数不一致也会导致 DATETIME 读取偏移。

解决:全链路统一用数值时间戳传参,后端转成 LocalDateTime 比较,MySQL 连接串显式加serverTimezone=Asia/Shanghai。前端展示时再把时间戳转成本地格式。论文里写“系统统一使用东八区时间”一句话,就能避免这个坑。

5.4 随机组卷生成的试卷总分不等于设定的总分

现象:教师设定总分 100 分,组卷成功后学生在端看到总分 84 分,或者考试结束后总分算出来是 112 分。

原因:组卷规则里“单选 5 题 × 每题 5 分 = 25 分,多选 5 题 × 每题 10 分 = 50 分”,但题库里符合条件的多选只有 4 道,代码使用Math.min抽了 4 道,总分自然少了 10 分。如果规则配置时每题分值乘数量没算对,也会超分。

解决:组卷完成后强制校验总分,不匹配就抛异常并提示教师调整规则。代码在 4.1 节已经有,核心就是actual.compareTo(rule.getTotalScore()) != 0这一行。更好一点的做法是设置一个容差区间,把误差控制在 0.5 分内,因为有些试卷的分数设置本身就不是整数。

5.5 学生交卷和系统自动交卷同时触发,成绩被覆盖

现象:考试截止时间前后,点击“交卷”按钮的瞬间,定时任务也扫描到这条超时记录,两边同时执行提交,最终成绩出现单题得分和总分对不上的情况。

原因:两个线程同时执行了 doSubmit。第一个线程更新了考试记录状态为已提交,第二个线程在更新前读取到了旧状态,又执行了同样的算分逻辑,数据被覆盖写。

解决:提交方法加乐观锁条件,更新语句带where status = 0,影响行数为 0 就说明已经被其他线程提交,直接返回。再用 Redis 的 setNX 命令加一个提交标记,谁先拿到标记谁执行。这两个手段同时使用,才能把这个并发问题彻底摁住。

6. 论文测试与验收:怎么把“系统测试”一章写出真实工作量

系统测试章节是最容易被写成流水账的地方。很多论文直接写“经测试,系统运行正常”,没有任何边界条件,答辩时老师随便给一个异常场景就答不上来。我的习惯是给测试章节建立三个固定动作。

第一个动作:每个核心功能必须写一条异常分支用例。登录接口要写“token 过期”,开考接口要写“未到开始时间”,交卷接口要写“重复提交”。这些用例数量不多,但比十条“正常登录成功”更有说服力。

第二个动作:单独做时间边界验证。把一场考试的开始时间设成当前时间的前一分钟,结束时间设成后一小时,然后分别在“开始前 10 秒”“考试中”“结束后 10 秒”三个时间点调用接口,检查系统的判断是否符合预期。

第三个动作:做双端并发验证。开两个浏览器同时登录同一个学生账号,两个窗口都点击进入同一场考试,检查系统是否只保留一份答题卡、另一个端口是否被标记为异常。

下面是一段可以放进论文的测试用例示例:

测试场景输入条件预期结果实测结果
未到开始时间进入考试开考前 10 分钟点击开始提示“考试尚未开始”通过
超过截止时间自动收卷考试结束 5 分钟后刷新页面自动提交并展示成绩通过
同账号双端登录两个浏览器同时进入考试后登录端标记异常,保留先登录端通过
作答中强刷页面考试中按 F5 重新加载答案从数据库恢复,不丢失通过

我第一次做这个题目时,只测了正常流程,对“断网”“双击”“重复提交”这类边界想都没想。后来被一位导师问“你的系统怎么防止学生考试时开第二个页面”,才发现测试章节的意义从来不是证明系统没有 bug,而是证明你想清楚了系统会怎么坏。从那以后我养成了一个习惯:每写一个接口,先想好它的异常输入,把这个输入记录在测试文档里,再开始写业务代码。这个方法后来在几次项目验收里都帮我避了雷。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 6:28:40

基于STM32L476和SX1262的低成本LoRaWAN单通道网关设计实践

这篇推文几乎把我的GitHub仓库和笔记翻了个底朝天&#xff0c;没想到最受关注的不是高深算法&#xff0c;而是一个我年初随手记录的LoRaWAN网关项目。项目本身不复杂——用STM32L476这颗MCU&#xff0c;配合一片SX1262射频芯片&#xff0c;做了一台轻量级的LoRaWAN单通道网关。…

作者头像 李华
网站建设 2026/10/22 6:27:58

智慧酒店规划方案核心:从客人动线到投资回报的工程落地指南

简介&#xff1a;一份聚焦铜陵智慧酒店建设的规划方案PPT共101页&#xff0c;面向酒店业主、智能化系统集成商、弱电工程师及规划设计人员&#xff0c;用于解决智慧酒店从概念构想到工程落地的方案衔接问题。方案围绕科技与艺术交融、舒适与节能平衡的整体思路&#xff0c;系统…

作者头像 李华
网站建设 2026/10/22 6:27:54

博文生成第一步:规范项目信息提交格式

明白。请提供你想要生成博文的项目信息&#xff0c;格式如下&#xff1a;项目标题: [标题] 项目正文: [零散原始描述&#xff0c;可以是任意领域内容] 关键词: [关键词1, 关键词2, ...] 摘要描述: [一句话简介]我拿到这些信息后&#xff0c;会严格按照规范输出一篇结构清晰、经…

作者头像 李华
网站建设 2026/10/22 6:27:54

WIDS车辆导航系统:平衡效率与安全的控制器设计实践

做车辆导航控制器最大的感受就是&#xff1a;效率和安全这两个指标&#xff0c;表面上不算冲突&#xff0c;实际一跑起来天天打架。你稍微把速度提一档&#xff0c;碰撞风险曲线立刻往上蹿&#xff1b;你为了安全把跟车距离拉大&#xff0c;后排乘客马上嫌你开得肉。我在做 WID…

作者头像 李华
网站建设 2026/10/22 6:27:43

基于SIFT与RANSAC的图像复制-移动伪造检测:Matlab实现与高分辨率实战

前阵子接了个图像真实性审查的活儿&#xff0c;对方送来一批高分辨率航拍样张&#xff0c;怀疑其中有人用复制粘贴的手段掩盖目标物。研究了一圈&#xff0c;最后选择用基于 SIFT 和 RANSAC 算法对高分辨率图像进行图像伪造检测&#xff0c;整套流程用 Matlab 搭建。这里把我的…

作者头像 李华
网站建设 2026/10/22 6:27:03

Servlet底层原理与生产级应用实践指南

1. 这不是“过时技术”的复习课&#xff0c;而是现代Web架构的底层解剖刀很多人看到“Servlet”三个字&#xff0c;第一反应是&#xff1a;这玩意儿不是十年前就该进博物馆了吗&#xff1f;Spring Boot一键生成、Node.js轻量路由、云原生Serverless函数满天飞——谁还手写doGet…

作者头像 李华