news 2026/9/23 14:20:12

在线考试系统源码实战:从数据库设计到自动判分避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在线考试系统源码实战:从数据库设计到自动判分避坑指南

简介:这份在线考试管理系统源代码,基于Java技术开发,面向需要完成课程设计或毕业设计的初学者与开发者,可解决传统考试流程繁琐、成绩统计耗时等问题。系统覆盖试题库管理、智能组卷、在线答题、成绩统计与权限控制等环节,代码应用了分层架构、依赖注入、对象关系映射、请求处理、动态页面、数据库设计、SQL优化、安全加密与防注入等多项技术,并将业务逻辑与界面展示解耦,完整呈现软件工程流程。压缩包共125个文件,大小约1.06MB,包含动态页面、静态页面、演示动图、依赖库、配置文件、编译字节码、数据库文件及部署包等,其中动图可直观演示操作,数据库文件存储试题与用户信息。目前已有1369人学习下载,适合作为毕业设计、课程设计或Java Web入门实践的参考,对理解工程结构、权限控制与数据库交互有直接帮助。

1. 这套系统到底是什么:别把它当成又一个 CRUD 作业

在线考试管理系统在 Java 项目里出现频率极高,不是因为技术多新,而是它天然覆盖了一门 Web 开发课该有的全部要素:用户角色权限、核心业务状态机、复杂查询、并发控制、事务边界。你拿到的源代码如果是单体 JSP/Servlet 版本,重点看 Session 管理和 JDBC 封装;如果是 Spring Boot + Vue 版本,重点看前后端分离下的接口设计和 Token 鉴权。这套系统的业务主链路非常清晰——管理员出卷、考生答题、系统判分、成绩归档——一条链路走完,基本把 Java Web 开发的主干技术过了一遍。适合两类人:正在找课程设计/毕业设计题目的在校生,以及公司内部要做培训考核、不想买 SaaS 产品的一线开发。前者关心代码结构和文档,后者关心能不能改、好不好部署。下面我按最常见的 SSM/Spring Boot 单体架构来讲,这也是大多数源码包的默认形态。

2. 技术选型和系统拆分:先看懂骨架,再动手改代码

2.1 为什么绝大多数源码选 SSM 或 Spring Boot 单体,而不是微服务

你下载的源码十有八九是 Spring Boot + MyBatis/MyBatis-Plus,或者老一点的 SSM(Spring + Spring MVC + MyBatis)。选单体是合理的,这套系统的并发量根本到不了需要微服务的程度。就算一个学校几千人同时在线考试,单体应用加个 Redis 缓存和数据库连接池调优就够用了。微服务在这里只会增加无意义的复杂度——你还要处理服务发现、配置中心、分布式事务,而这些对考试系统没有任何业务收益。Spring Boot 相对 SSM 的最大优势是零 XML 配置,内嵌 Tomcat,java -jar 就能跑,这对学生党部署和演示是实打实的省事。

MyBatis 在这类系统里的占比高于 JPA,原因是考试系统的 SQL 普遍偏复杂:多表联查(试卷表、试题表、考试记录表)、动态条件(按题型、难度、知识点筛选)、批量插入(批量导入试题、批量提交答案)。MyBatis 的 XML 映射文件写动态 SQL 非常顺手,而 JPA 在这种场景下要么写 @Query 注解里拼接 JPQL,要么用 Specification,可读性反而差。如果你是拿源码改,优先确认持久层用的是 MyBatis 还是 MyBatis-Plus——后者自带 BaseMapper,单表 CRUD 不用写 SQL,能省不少事。

2.2 标准模块划分:七个模块,一条考试主流程

考试系统无论前端界面长什么样,后端模块基本跑不出下面这张表。拿到源码第一步不是跑起来,而是对照这张表找到对应代码位置。

模块核心实体关键技术点典型代码位置
用户认证User, RoleSession/Token、MD5/BCrypt、拦截器controller/LoginController, interceptor/
题库管理Question, QuestionType批量导入、多条件查询、题目去重controller/QuestionController, service/
试卷管理Paper, PaperQuestion手工组卷/随机组卷、题目分值分配controller/PaperController, service/impl/
在线考试ExamRecord, ExamAnswer倒计时、答案暂存、交卷事务controller/ExamController, service/
自动判分无独立实体客观题比对、主观题人工评分service/GradeService
成绩管理Score, GradeStatistic成绩导出、及格率统计controller/ScoreController
系统管理Menu, Log菜单权限、操作日志controller/SystemController

拿到源码后按这张表去定位代码,能节省至少两小时的摸索时间。判分逻辑不要在 Controller 里一层层找,直接在 service 包下搜 "Grade" 开头的类,最省事。

2.3 拿到源码后的第一步:快速确认它能不能跑

不管源码是从哪里下的,先做三件事,确认项目能启动再谈改代码。第一,看 pom.xml(Maven)或 build.gradle(Gradle)里的依赖版本,确认 JDK 版本匹配——Spring Boot 2.x 要 JDK 8 或 11,Spring Boot 3.x 必须 JDK 17+,版本不匹配你连编译都过不了。第二,找 application.yml 或 application.properties,看数据库连接配置连的是 MySQL 还是 PostgreSQL,端口是多少,Redis 有没有。第三,看有没有 sql 目录或 db 目录,数据库初始化脚本在不在。这三个确认完,项目能不能跑你心里就有数了。

# 第一步:检查项目依赖和 JDK 版本 cd exam-system cat pom.xml | grep -E "(java.version|spring-boot-starter-parent)" # 输出示例:<java.version>1.8</java.version>,说明需要 JDK 8 # 第二步:查看数据库配置文件 cat src/main/resources/application.yml | grep -A 10 "datasource" # 确认 url、username、password 三个关键项 # 第三步:确认数据库脚本存在 ls -la sql/ 或者 find . -name "*.sql" -type f

这一步不是玄学,是血泪经验。我遇到过不止一次,源码包里的 SQL 脚本是空文件,或者数据库名和配置文件里对不上,导致项目启动成功但登录时报"Table doesn't exist"。先确认这些,比急着点 Run 按钮靠谱得多。

3. 数据库设计与初始化:考试系统的地基,六个关键表不能少

3.1 核心表结构:从用户到成绩的完整数据链路

考试系统的数据库表设计,是所有代码的基础。表设计得烂,后面写再漂亮的代码也补不回来。最常见的表结构包含六张核心表:用户表、角色表、试题表、试卷表、考试记录表、答案表。其中最容易出问题的不是用户表,而是试卷表和试题表的关系,以及考试记录表和答案表的关系。前者是多对多,需要中间表;后者是一对多,考试记录表作为主表,答案表存每题作答明细。

下面是最常见的一个建表 SQL 片段,对应的是 MySQL 8.x。如果你的源码用的是 5.7,把 utf8mb4_0900_ai_ci 改成 utf8mb4_general_ci 就行。

-- 用户表 CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `role_id` bigint(20) NOT NULL COMMENT '角色ID 1管理员 2教师 3学生', `status` tinyint(4) DEFAULT '1' COMMENT '1启用 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 试卷表 CREATE TABLE `exam_paper` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `paper_name` varchar(100) NOT NULL COMMENT '试卷名称', `subject_id` bigint(20) DEFAULT NULL COMMENT '科目ID', `total_score` int(11) DEFAULT '100', `duration_minutes` int(11) DEFAULT '60' COMMENT '考试时长(分钟)', `pass_score` int(11) DEFAULT '60' COMMENT '及格分', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `status` tinyint(4) DEFAULT '0' COMMENT '0草稿 1已发布 2已结束', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试卷表'; -- 试卷-试题关联表(核心中间表) CREATE TABLE `exam_paper_question` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `paper_id` bigint(20) NOT NULL COMMENT '试卷ID', `question_id` bigint(20) NOT NULL COMMENT '试题ID', `question_score` int(11) NOT NULL COMMENT '该题分值', `sort_order` int(11) DEFAULT '0' COMMENT '题目顺序', PRIMARY KEY (`id`), KEY `idx_paper_id` (`paper_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试卷题目关联表';

3.2 考试记录表和答案表:事务与并发控制的关键

考试记录表和答案表是这套系统里事务最密集的地方。考生点击"交卷"按钮的那一刻,后端要做的事包括:校验考试时间是否超时、计算客观题得分、关联主观题给分、更新考试记录状态、写入每道题的答案明细、更新成绩表。这一串操作必须在一个事务里完成,否则可能出现"成绩表更新了但答案明细没写入"的数据不一致问题。

-- 考试记录表 CREATE TABLE `exam_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `exam_code` varchar(32) NOT NULL COMMENT '考试编号,UUID', `paper_id` bigint(20) NOT NULL, `student_id` bigint(20) NOT NULL COMMENT '考生用户ID', `start_time` datetime DEFAULT NULL, `submit_time` datetime DEFAULT NULL, `objective_score` decimal(5,2) DEFAULT '0.00' COMMENT '客观题得分', `subjective_score` decimal(5,2) DEFAULT '0.00' COMMENT '主观题得分', `total_score` decimal(5,2) DEFAULT '0.00', `status` tinyint(4) DEFAULT '0' COMMENT '0未开始 1考试中 2已交卷 3已判分 4缺考', PRIMARY KEY (`id`), UNIQUE KEY `uk_exam_code` (`exam_code`), KEY `idx_paper_student` (`paper_id`, `student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试记录表'; -- 答题明细表 CREATE TABLE `exam_answer_detail` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `record_id` bigint(20) NOT NULL COMMENT '考试记录ID', `question_id` bigint(20) NOT NULL, `student_answer` text COMMENT '考生作答内容', `correct_answer` text COMMENT '正确答案(冗余存储,判分用)', `is_correct` tinyint(4) DEFAULT '0' COMMENT '0错误 1正确(主观题为0)', `score` decimal(5,2) DEFAULT '0.00' COMMENT '本题得分', PRIMARY KEY (`id`), KEY `idx_record_id` (`record_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试答题明细表';

考试系统的性能瓶颈几乎都在这两张表上。索引要加在 record_id 上,因为判分和成绩查询都以 record_id 为入口。另外,exam_answer_detail 里的 correct_answer 字段是冗余存储,这在设计上是有意为之——考试结束后如果要改题库,正确答案变了,已考过的试卷成绩不能被影响,所以判分那一刻的正确答案必须快照到明细表里。这个细节如果你要改源码,千万别"优化"掉。

3.3 数据库初始化脚本的顺序问题

拿到 SQL 脚本后,导入顺序很重要。先建库,再建表,最后插入初始化数据(尤其管理员账号)。很多源码自带的 SQL 脚本是混在一起的,你需要自己理清顺序。如果你用的是 Navicat 或 DataGrip,直接执行整个 .sql 文件通常没问题,因为脚本顶部一般有 CREATE DATABASE IF NOT EXISTS 语句。但有个高频坑:字符集。如果脚本里的中文注释乱码,十有八九是连接数据库时的字符集没指定 utf8mb4,需要在 JDBC 连接串上加上 characterEncoding=utf8mb4。

# JDBC 连接串参考(application.yml 里的 datasource.url) jdbc:mysql://localhost:3306/exam_system?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

参数说明:serverTimezone 必须指定,MySQL 8.x 默认时区是 UTC,不指定的话你存进去的时间和本地时间差 8 小时;allowPublicKeyRetrieval 是 MySQL 8.x 用 caching_sha2_password 认证时需要的,不加会报 "Public Key Retrieval is not allowed" 错误。这两个参数是新手最容易卡住的点。

4. 核心功能落地:登录鉴权、随机组卷和自动判分怎么写

4.1 登录鉴权:Session 还是 Token,源码里两种都有

老一点的 JSP/Servlet 版本清一色用 Session,Spring Boot + 前后端分离版本基本用 Token(JWT)。如果你拿到的是前者,登录接口在 LoginServlet 或 LoginController 里,逻辑非常简单:查用户表 → 比对密码 → 把 user 对象塞进 Session → 跳转到首页。这种做法的缺点是集群部署时 Session 不共享,需要引入 Spring Session + Redis,但对单体应用来说足够了。

// 基于 JWT 的登录鉴权核心代码(Spring Boot 版) @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 根据用户名查询用户 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getUsername, dto.getUsername()); SysUser user = userMapper.selectOne(wrapper); // 2. 用户名不存在,直接返回错误 if (user == null) { return Result.error("用户名或密码错误"); } // 3. BCrypt 密码比对,注意不能用 MD5 直接比较 if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 4. 检查账号状态 if (user.getStatus() == 0) { return Result.error("账号已被禁用,请联系管理员"); } // 5. 生成 JWT Token,有效期 2 小时 String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRoleId()); return Result.success(token); }

这段代码解决了两个常见问题:为什么用 BCrypt 而不是 MD5——MD5 撞库太容易,加盐(BCrypt 内置随机盐)是底线,很多网上的老源码还在用 MD5,你要改的第一件事就是这个;为什么登录失败统一返回"用户名或密码错误"——避免通过返回信息差异来枚举用户名。

4.2 随机组卷:从题库抽题的核心算法

组卷是这个系统的灵魂功能,面试时也常考。随机组卷需求非常简单:从指定科目和题型下随机抽取 N 道题,每道题分值固定。但实现上有三个边界条件要处理:题目数量不够时要提示,而不是抽到一半报空指针;不同题型要分开查,不能一条 SQL 搞定;抽题后要校验总分和设定的总分一致。

// 随机组卷核心逻辑:按题型分组抽题 public boolean generateRandomPaper(PaperDTO dto) { // dto 里包含:科目ID、每种题型的数量和分值、试卷总时长 List<PaperQuestion> paperQuestions = new ArrayList<>(); int totalScore = 0; // 1. 遍历题型配置,逐个题型抽题 for (QuestionTypeConfig config : dto.getTypeConfigs()) { // 按题型和科目从题库随机抽取 List<Question> questions = questionMapper.selectRandomByType( dto.getSubjectId(), config.getQuestionType(), config.getCount() ); // 2. 边界校验:题库数量不足要抛异常 if (questions.size() < config.getCount()) { throw new BizException( "题型[" + config.getQuestionTypeName() + "]题库数量不足,需要[" + config.getCount() + "]道,实际只有[" + questions.size() + "]道" ); } // 3. 组装试卷-题目关联关系 for (int i = 0; i < questions.size(); i++) { PaperQuestion pq = new PaperQuestion(); pq.setPaperId(dto.getPaperId()); pq.setQuestionId(questions.get(i).getId()); pq.setQuestionScore(config.getScore()); pq.setSortOrder(i); paperQuestions.add(pq); totalScore += config.getScore(); } } // 4. 总分校验:防止组卷后总分不等于 100 if (totalScore != dto.getTotalScore()) { throw new BizException("组卷总分异常,配置总分[" + dto.getTotalScore() + "],实际总分[" + totalScore + "]"); } // 5. 批量插入关联表 paperQuestionMapper.batchInsert(paperQuestions); return true; }

这里的关键不是抽题 SQL,而是第 2 步和第 4 步的两个校验。题库数量不足时如果没有提示,生成的试卷就会缺题,考生交卷时判分逻辑也会出错;总分不校验的话,会出现考生明明全做对了,成绩却是 98 分而不是 100 分的诡异情况。前者是开发阶段必踩的坑,后者是对外演示时最容易翻车的细节。

SQL 实现上,随机抽题的写法也有讲究:

-- 按题型随机抽取 N 道题(MySQL) SELECT * FROM exam_question WHERE subject_id = #{subjectId} AND question_type = #{questionType} ORDER BY RAND() LIMIT #{count}

ORDER BY RAND() 在数据量小(几千题)时没有问题,但题库超过十万道时性能会明显下降。高并发内部系统的替代方案是:先 SELECT COUNT(*) 拿总数,再用随机偏移量查。但对于课程设计级别的源码,RAND() 够用了,不用过度优化。

4.3 自动判分:客观题秒判,主观题走人工

判分逻辑的代码位置在不同源码里差别很大,但核心思路大致是:交卷时遍历考试记录关联的所有答题明细,对选择题、判断题、填空题逐一比对答案。选择题和判断题用 equals 比,填空题要考虑学生答案的首尾空格——这个细节踩坑率极高,很多学生的答案后面带个空格,导致正确答案也被判错。

// 客观题自动判分核心逻辑 public void gradeObjectiveQuestions(Long recordId) { // 1. 查询该考试记录的所有答题明细 List<ExamAnswerDetail> details = answerDetailMapper.selectByRecordId(recordId); // 2. 遍历判分 double objectiveScore = 0.0; for (ExamAnswerDetail detail : details) { // 跳过主观题(简答题/论述题) if (detail.getQuestionType() == 3) { // 假设 3=主观题 continue; } // 3. 答案比对前先 trim,去首尾空格 String studentAnswer = detail.getStudentAnswer() == null ? "" : detail.getStudentAnswer().trim(); String correctAnswer = detail.getCorrectAnswer() == null ? "" : detail.getCorrectAnswer().trim(); // 4. 比对并打分 boolean isCorrect = studentAnswer.equalsIgnoreCase(correctAnswer); detail.setIsCorrect(isCorrect ? 1 : 0); detail.setScore(isCorrect ? detail.getQuestionScore() : 0.0); objectiveScore += detail.getScore(); // 5. 标记主观题待人工评分 detail.setIsCorrect(0); // 主观题不判对错,只标 0 answerDetailMapper.updateById(detail); } // 6. 更新考试记录表的客观题得分 ExamRecord record = examRecordMapper.selectById(recordId); record.setObjectiveScore(objectiveScore); record.setStatus(2); // 已交卷待判分 examRecordMapper.updateById(record); }

有几个源码常见的坑:比较答案用了 == 而不是 equals,导致字符串内容相同但判错;没处理 null,考生没作答时 studentAnswer 为 null,直接调用 trim() 抛空指针;主观题没跳过,把文本答案和参考答案用 equals 比对,结果全判错。拿到源码后优先检查这三处,比通读全部代码更有价值。

5. 避坑指南:在线考试系统最容易翻车的五个地方

5.1 时不时的 404:前端路由刷新就白屏

现象:用前后端分离版本部署时,考生在考试页面按 F5 刷新,直接变成 404 页面。原因:前端是 Vue Router 的 history 模式,路由由前端管理,但 Nginx 没有配置 try_files 回退到 index.html。解决:在 Nginx 的 location / 块里加上如下配置。

location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }

这个配置让所有找不到的后端路径都回退到 index.html,由前端路由接管。注意,/api 开头的请求不能走这个规则,要单独配置 proxy_pass 转发到后端服务。

5.2 交卷后成绩一直是 0:事务提交顺序惹的祸

现象:考生交卷后,考试记录状态显示已交卷,但客观题得分为 0,成绩没算出来。原因:判分逻辑先更新了考试记录表,再逐条更新答题明细表,但答题明细更新失败或没有在同一个事务里,导致记录表更新了、分数却丢了。解决:确认判分的入口方法上有 @Transactional 注解,并且 Service 层所有对数据库的写操作都在这个事务内。

// 正确写法:交卷和判分必须在一个事务里 @Transactional(rollbackFor = Exception.class) public void submitExam(SubmitExamDTO dto) { // 1. 校验考试时间 // 2. 保存答题明细 // 3. 判分 // 4. 更新考试记录状态和分数 }

rollbackFor = Exception.class 是必须的,不然 Spring 默认只在遇到 RuntimeException 时回滚,某些自定义异常会导致事务不生效。这是老源码里非常隐蔽的问题。

5.3 考试中途 Session 过期,答完的题全丢了

现象:考生答了 40 分钟,点提交时提示登录已过期,跳回登录页,回来发现答题内容全没了。原因:Session 默认超时时间是 30 分钟,考试时长 60 分钟,考生答题中途 Session 就失效了。解决:在 application.yml 里调大 Session 超时时间,或者改用 JWT,Token 有效期拉长到考试时长加缓冲。

server: servlet: session: timeout: 120m # 单位是分钟,m 不能漏

如果用的是 JWT,生成 Token 时把过期时间设成当前时间 + 考试时长 + 30 分钟缓冲。另外,考试页面要做答案自动暂存——每隔 60 秒把已答内容通过 AJAX 发送到后端草稿表,防止浏览器崩溃或断电导致答案丢失。这个功能叫"防断网答题",在源码里一般对应 answer_draft 表或 Redis 缓存。

5.4 时间校验:客户端时间能改,别信它

现象:考生把电脑系统时间调慢两小时,考试倒计时就变多了,可以慢慢百度答案。原因:倒计时逻辑写在前端,用的是 new Date() 读取本地时间。解决:倒计时要由后端下发服务器时间,前端只做展示和每秒减一,交卷时后端再校验一次实际耗时是否超过考试时长。

// 后端交卷时校验考试时长 public void checkExamDuration(ExamRecord record) { long startTime = record.getStartTime().getTime(); long submitTime = System.currentTimeMillis(); int durationMinutes = record.getDurationMinutes(); long maxAllowedMinutes = durationMinutes + 2; // 2 分钟缓冲 if ((submitTime - startTime) > maxAllowedMinutes * 60 * 1000) { throw new BizException("考试超时,系统已自动提交"); } }

这里的 2 分钟缓冲不是多余的,要考虑网络延迟和考生提交时的正常操作耗时。没有缓冲的话,考生 59 分 59 秒点交卷,后端一校验超时就报错,体验很差。

5.5 数据库连接池耗尽:一次考试全部卡死

现象:200 人同时交卷,系统直接卡死,后台日志报 "Connection is not available, request timed out"。原因:连接池最大连接数配得太小,默认 HikariCP 是 10,200 个并发请求同时占用连接,连接池被占满,后续请求全部排队超时。解决:估算并发量,调大连接池,同时优化持有连接的时间。

spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000 max-lifetime: 1800000

不要无脑调到 100,连接数越多,数据库压力越大,反而更慢。建议按实际并发量估算,公式是:峰值并发数 × 单请求平均耗时(秒) / 数据库平均响应时间(秒)。内部系统通常会高估,30 到 50 足够。

6. 部署与验证:用一套完整流程确认源码可用,再谈二次开发

源码能跑通只是第一步,真正要上线给考生用,还要过一遍部署和验证流程。我一般会在本地先跑通开发环境,再切到生产环境配置(本文不涉及网络代理相关的内容),最后做一次全流程功能回归。这套方法对任何考试系统源码都适用。

先说部署参数。生产环境要把 application.yml 里的几个配置改掉:数据库密码换成强密码,不能用 root 账号连接数据库;JWT 密钥换掉源码自带的默认值,否则任何人都能伪造 Token;日志级别从 DEBUG 改成 INFO,考试系统的日志量非常大,DEBUG 级别一天能写几个 G 磁盘。

代码混淆工具在项目上线前值得考虑,尤其是题库和判分逻辑是核心资产时。

# 打包命令(跳过单元测试) mvn clean package -DskipTests # 启动 Java 程序 java -jar target/exam-system-1.0.0.jar --spring.profiles.active=prod # 查看启动日志,确认端口和数据库连接正常 tail -f logs/exam-system.log

验证流程我建议按"主链路 → 异常链路 → 边界场景"三层来测,全部过了再放给真实用户。主链路是管理员登录创建试卷、导入试题、发布考试;考生登录参加考试、答题交卷;教师登录判主观题、发布成绩。异常链路是考生交卷超时、断网重连、重复提交。边界场景是零题量的空试卷、考试时长只有 10 分钟的极限配置、题库只有 1 道题时随机组卷还能不能出卷。

# 模拟 200 并发同时交卷的简单压测(用 wrk 或 ab) ab -n 200 -c 20 -p submit_payload.json -T application/json http://localhost:8080/api/exam/submit

压测关注两个指标:请求失败率为 0,平均响应时间在 2 秒内。如果超时,优先看数据库连接池和慢 SQL 日志,而不是急着加服务器配置。大部分考试系统卡顿不是硬件不够,是 SQL 没走索引。

最后一件事,是给这套源码做备份。数据库脚本、源码、部署配置、压测结果,放在一起打压缩包,写一个 README 记录部署步骤。这不是形式主义,而是我吃过亏——项目跑了一个学期,期中的时候数据库被误删,恢复数据花了一天一夜。备份文件别放在 Tomcat 的 webapps 目录下,万一被当成静态资源下载就麻烦了,放到专门的备份目录,权限收紧。

如果你拿到的源码里前端路由配置和后端跨域配置对不上——这种情况还挺常见的,优先看后端有没有加 CORS 配置,没有的话在 Spring Security 或拦截器里加上。希望这篇笔记能帮你在一堆代码里少走点弯路。

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

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

深度学习信道编码:基于自编码器的PyTorch实现与工程实践

简介&#xff1a;面向通信工程与深度学习交叉领域的学习者和研究人员&#xff0c;资源围绕“深度学习驱动的信道编码与解码”主题&#xff0c;针对传统Turbo码、LDPC等方案在复杂信道下难以灵活适配的问题&#xff0c;演示如何利用神经网络自动学习信道特征并优化纠错性能。内置…

作者头像 李华
网站建设 2026/9/23 14:13:19

AMT630H屏驱SoC数据手册解读:Cortex-A5与2D加速器实战

简介&#xff1a;AMT630H数据手册面向从事屏驱开发、嵌入式硬件与单片机应用的工程师&#xff0c;尤其适合使用STM32、ARM架构平台进行高清显示控制器选型与调试的读者。该芯片属AMT系列第三代产品&#xff0c;内核为Cortex-A5&#xff0c;主频最高500MHz&#xff0c;内置32MB …

作者头像 李华
网站建设 2026/9/23 14:13:11

纯模拟水温控制回路设计:从传感器放大到迟滞比较器的工程实践

简介&#xff1a;这份资源是一份面向电子信息、自动化等专业学生的课程设计文档&#xff0c;围绕「简易水温控制系统设计」展开&#xff0c;适合正在做电子技术课程设计或想练习传感器与自动控制结合实践的学习者。压缩包内共1个doc文件&#xff0c;约965KB&#xff0c;内容为完…

作者头像 李华
网站建设 2026/9/23 14:11:48

TCP-RDT3.0 仿真工程:从超时重传到滑动窗口的可靠传输实现

简介&#xff1a;TCP-RDT3.0.zip 是一份面向计算机网络课程学习者与实验教学场景的配套资料&#xff0c;围绕可靠数据传输协议 RDT 3.0 展开&#xff0c;适合正在理解 TCP 底层机制、需要动手实现停等 ARQ 与差错恢复逻辑的学生或教师使用。压缩包共 16 个文件&#xff0c;约 1…

作者头像 李华