☰
SpringBoot2+Vue3+MyBatis-Plus在线考试系统开发实战详解
2026/10/3 3:14:42 网站建设 项目流程

考试系统这个选题,在 Java Web 方向里算是“常青树”了。从十多年前的 JSP/Servlet 时代,到现在的 SpringBoot 前后端分离,它始终是毕业设计、课程设计里出镜率最高的项目之一。原因其实不复杂:它虽然看起来很教学,但业务边界非常清晰,从用户登录、权限控制、题库管理,到在线答题、自动计时、评分统计,一条线走下来,前端和后端该处理的真实问题一个都不少,非常适合用来检验一个人是否具备完整的 Web 开发能力。

标题里这套 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0 的组合,是这两年最常见的搭配,几乎成了课设项目的“标准答案”。SpringBoot2 负责后端接口,Vue3 负责页面交互,MyBatis-Plus 把 CRUD 从体力活变成半自动流水线,MySQL8.0 作为数据底座稳定托底。再加上项目自带文档,这套源码非常适合两类人:一类是正在做毕设或课设的同学,需要快速理解全流程,并且真正跑起来一套能演示的代码;另一类是学校、培训机构或企业内部想搭一套在线考试系统,希望找一个可靠基线去做二次开发的工程师。

这篇文章不贴完整源码,而是把这个项目里面真正值得下功夫的地方讲透:数据库表该建几张、每张表的关键字段怎么设计、JWT 登录怎么做、随机组卷和自动评分如何实现、前端哪些地方容易踩坑、部署到服务器又要改哪几个配置。把这些弄明白了,你能答辩顺利,也能快速把它魔改成自己想要的版本。

1. 项目概述与需求拆解

1.1 考试系统的核心角色与业务流程

在线考试系统本质上是一个“人—题—成绩”的管理系统。人分三种:管理员、教师、学生。题就是题库里的各类题目。成绩是考试结束之后产生的记录和统计。这个定位看着简单,但如果你一上来就打开 Navicat 建表,大概率会在第三张表开始反复删改,因为很多关联关系没有提前理清楚。

先看角色。管理员负责系统级配置,比如用户管理、科目管理、角色分配;教师负责业务内容,比如维护题库、配置试卷、查看成绩统计;学生是系统的主要使用者,登录后选择考试、答题、提交、查看成绩。三种角色的权限边界必须在立项时就明确,不然后端接口会变成一团浆糊。

再看业务流程。核心链路就是:登录认证 → 选择试卷 → 进入考试 → 逐题作答 → 倒计时结束或主动提交 → 客观题自动评分 → 主观题教师批改 → 成绩入库 → 成绩查询和统计。整条链路里,状态流转是最容易忽略的。一张试卷或一场考试,至少要区分“未开始”“进行中”“已结束”三种状态,前端按钮的显示、后端接口的可调用性,都要跟着状态走。

1.2 搞清需求边界比写代码更早

我见过不少同学拿到需求就急着写代码,结果做到一半发现组卷方式跟数据库设计冲突,或者权限漏洞百出。这类项目最忌讳“上来就写”。你要先画两张图:一张是角色用例图,一张是核心流程的时序图。不用画得多标准,哪怕是白纸上手绘都行,能帮你把“谁做什么,数据怎么流”梳理清楚。

需求边界里要重点确认的问题包括:题目类型有哪些。单选、多选、判断是最基础的,如果加简答题,就要有主观题评分机制,这意味着教师端要出现一个“批改”页面。组卷模式是手动组卷还是随机抽题。随机抽题需要题库表里存难度系数和知识点字段,抽题逻辑要按难度和知识点分布去取。是否允许考试中途退出再进入。如果不允许,前端要做二次确认,后端要处理重复提交的幂等。是否要做防舞弊。简单版本可以记录切屏次数,进阶版本可以做摄像头抓拍,但这些都会增加大量复杂度,毕设做基础版就够了。

把这些边界问题确认清楚,你后面所有表设计和接口设计都会顺畅很多。经验之谈:这时候花 1 天梳理需求,后面能省 3 天返工。

2. 技术选型深度解析

2.1 SpringBoot2:它依然是后端主力

很多人纠结为什么不是 SpringBoot3。答案很简单:生态兼容性。SpringBoot3 要求 JDK17 起步,很多老牌库还没有完全跟上,而 SpringBoot2 天生配合 JDK8 或 JDK11,网上能查到的资料、踩坑帖子、毕设参考案例都最多。对一个需要快速上线、快速被评审老师看明白的项目来说,SpringBoot2 是最稳妥的。

SpringBoot2 的价值在于“约定大于配置”。内嵌 Tomcat,不用你再去部署一个独立的 Web 容器;自动配置能力让你只需要在 application.yml 里写少量配置,就能把数据源、MyBatis、事务管理全部拉起来。考试系统里大量接口是标准的增删改查,SpringBoot 的 Controller + Service + Mapper 三层结构非常规整,新同学能看懂,有经验的人也不觉得别扭。

用 SpringBoot2 还有一个实际好处:Spring Security 或 Shiro 这类安全框架都有成熟版本。不过说实话,毕设级别项目我建议自己用 JWT + 拦截器实现认证权限,代码量不大,而且每个细节你都能在答辩时讲清楚。用了重量级安全框架,反而容易在配置上翻车。

2.2 Vue3 与组合式 API:前端体验的升级

Vue3 出来之后,前端代码组织方式发生了非常大的变化。最核心的就是 Composition API。以前用 Options API,代码是按 data、methods、computed、watch 这些选项分块的,一个功能相关的代码可能被拆散到不同位置;现在用组合式 API,你可以把一个考试倒计时、答题状态、提交逻辑全部收拢在一起,维护性明显提升。

现在 Vue3 项目基本都是写<script setup>语法糖,配合 ref、reactive、computed、watchEffect 这几个核心 API。ref 和 reactive 处理响应式数据,computed 处理派生状态,watch 监听变化。考试系统里最适合展示组合式 API 优势的场景就是答题页面:当前题目索引、用户答案列表、剩余时间、答题进度,这些状态相互关联,用原生ref和computed可以写得很简洁。

还要提一下构建工具。Vue3 的默认配套是 Vite,而不是 Vue2 时代的 Webpack。Vite 开发环境下启动几乎秒开,热更新也快,对于反复调样式、调接口的联调阶段非常友好。唯一要注意的是 Vite 构建后的资源路径问题,默认 base 是根路径/,部署到二级目录时要把base改成相对路径或实际子路径,不然页面会白屏。

2.3 MyBatis-Plus 与 MySQL8.0:省心省力的数据层组合

MyBatis-Plus 不是给“懒人”用的,而是把单表 CRUD 的效率提到极致。继承一个BaseMapper<T>,selectById、insert、updateById、deleteById这些方法直接就能用,你用不着为每张表写一大堆 XML 映射文件。考试系统里像题库增删改查、考试记录查询、用户管理这些接口,大量就是单表操作,用 MyBatis-Plus 能省一半代码量。

更实用的是条件构造器。比如“按科目查题目并按难度排序”,用LambdaQueryWrapper一行就写出来,不用拼 SQL 字符串。动态条件判断用condition参数,传值为 null 的时候自动跳过条件,这个特性在处理查询条件时太好用了。

MySQL8.0 相比 5.7 有几个实际改进。第一是默认字符集为utf8mb4,可以把 emoji 和生僻字完整存下来,现在用户名备注里出现特殊符号是很常见的事,用 5.7 老配置会报字符集错误。第二是支持窗口函数,如果要写“学生成绩排名,按班级分组取前三名”这种统计 SQL,会用窗口函数可以简短很多。第三是持久化配置更规范,如果要用 MySQL8.0,注意 JDBC 驱动要配com.mysql.cj.jdbc.Driver,而且 URL 里强烈建议加上serverTimezone=Asia/Shanghai,否则日期时间不同步会让你排查到崩溃。

3. 核心功能模块设计与实现

3.1 JWT 认证与角色权限控制

这个模块是考试系统的安全底线。登录成功后,后端生成一个 JWT token 返回给前端,前端存在本地存储里;后续每次请求,前端都要在请求头里带上Authorization: Bearer <token>。后端写一个拦截器统一校验 token 是否有效、是否过期,再把当前用户信息塞到 ThreadLocal 里,供后续业务代码直接读取。

JWT 生成建议用 java-jwt 或 hutool 里的 JWT 工具,都可以。Payload 里放 userId、userName、role 这三个核心字段就行,密码绝不能放。过期时间不要设太长,考试系统一般 2 到 4 小时即可;如果业务上有“记住我”的需求,再考虑 refresh token,但毕设阶段做成单 token 就可以了。

角色权限方面,我不建议引入太重的框架。可以定义一个自定义注解,比如@RequireRole("teacher"),在拦截器里读取请求 HandlerMethod 上的注解,比对 JWT 里的角色字段,不一致就返回 403。这样管理员、教师、学生的接口权限就能轻松控制住。拦截器放行规则也要留意,登录接口、前端静态资源这些必须放行,不然会出现死循环。

前端同样需要路由守卫。Vue Router 的beforeEach里判断有没有 token,没有就跳登录页;进入考试页面之前,再调后端接口确认当前用户有没有权限参加这场考试。前后端两层都做控制,才算一个完整的认证方案。

3.2 题库管理、组卷策略与试卷渲染

题库管理最核心的实体是“题目”。题目包含的类型、题干、选项、答案、解析、知识点、难度、所属科目这些字段,是后面组卷和评分的依据。管理端要支持多选题型的增删改查,题目最好支持批量导入,我做过一个 Excel 导入版本,用 EasyExcel 就能实现,方便教师一次性录入几百道题。

组卷策略有手动和随机两种。手动组卷是指教师从题库里勾选题目,设定分值,存进试卷;随机组卷是教师设置试卷配置,比如单选题 20 道、多选题 10 道、判断题 10 道,并且指定难度占比,系统从题库里随机抽取。考试系统通常会做随机组卷,因为每个学生抽到的题可能不同,可以有效减少作弊。

随机组卷的实现思路是:试卷表存“试卷配置”,试卷与题目通过一张关联表记录。抽题时按科目、题目类型、难度分组查题库,每组按需要的数量取随机记录,然后插入试卷题目关联表。MyBatis-Plus 里可以用last("ORDER BY RAND() LIMIT " + count)做随机查询,数据量在一万道以内性能没有压力,这也是一种最常见的实现。

试卷渲染要看前端功底。拿到试卷结构后,前端要把不同题型渲染成不同组件:单选题用 radio,多选题用 checkbox,判断题用 radio,简答题用 textarea。用户选择后,答案要实时同步到响应式对象里。我建议用“题目索引 -> 用户答案”映射的 Map 结构,不要用二维数组,否则每道题的值要手动绑定索引,代码会很难看。

3.3 考试计时、自动交卷与自动评分

考试计时是学生端最容易出 bug 的地方。最忌讳的做法是前端倒计时到 0 就本地提交,因为用户可以改本地时间。正确做法是:后端在考试开始接口里返回服务器当前时间和考试时长,前端用这两个值计算剩余秒数,只做展示;倒计时结束或用户主动提交时,统一走后端提交接口,后端校验时间是否超时。这才是可靠的计时方案。

自动交卷有两层:前端倒计时归零时,调用提交接口,同时把本地已答的题发送过去;后端接口里还要做兜底,判断当前时间如果已经超过了考试截止时间,就强制按已提交处理。这里要处理好重复提交的问题,最简单的方案是考试记录表给user_id + exam_id建唯一索引,第二次插入或者更新直接返回冲突。

自动评分针对客观题,单选题、判断题、多选题都是比对答案字符串。单选题判断如果用户答案等于正确答案就对,多选题要注意答案顺序,存储时统一用固定分隔符排序,比如“A,B,C”存好,比对时拆开排序再拼回去,避免因顺序不同误判。答对得分、答错 0 分,部分多选要不要给一半分,这个是要跟需求确定的细节,推荐做成配置项。

主观题评分需要教师角色介入。学生提交答卷后,记录状态是“待批改”,教师端可以看到简答题列表,逐题打分,最终成绩 = 客观题得分 + 主观题得分。这个过程要实现得完整,就又牵连出一道题“成绩合不算”,其实逻辑不复杂,关键是要把状态机设计好。

3.4 成绩统计与教师批改

成绩统计这块,很多项目会低估它的工作量。最基础的是“成绩查询”,学生能看到自己的历史成绩;教师能看到某一场考试所有学生成绩的列表,并且可以导出 Excel。再往上一点,是班级平均分、最高分、最低分、及格率、分数段分布。这些 SQL 写熟练了其实不难,难点是要在前面数据库设计里把考试记录表和班级信息表预留好关联字段。

教师批改功能的实现,简单说就是:先把客观题分数统计出来,然后把一考生的简答题答案展示给教师,教师输入分数后,后端更新考试记录的总分和状态。这里推荐做一个小优化:批改时直接用事务把客观题分数和主观题分数一起锁住更新,避免两个人同时批改同一份答卷。不用搞得很复杂,select ... for update就能解决问题。

4. 数据库设计与核心表结构

4.1 整体表设计与关联关系

这一节是很多同学最头疼的地方,因为表一多就乱。我先给你一个经过实践检验的表清单:用户表 users、科目表 subjects、题库表 questions、试卷表 exam_papers、试卷题目关联表 exam_paper_questions、考试记录表 exam_records、学生答题详情表 answer_details、操作日志表 sys_logs。一共八张表,核心业务六张,日志表看需求加。

关联关系是这样走:users 和 subjects 是多对多,一个学生可以选多门课,一门课有多个学生,但如果你不打算做选课系统,可以直接在 users 表里加一个subject_id字段简化成一对多。questions 属于某个 subject,exam_papers 也属于某个 subject。exam_papers 和 questions 是多对多,通过 exam_paper_questions 关联,并且这个关联表要存“该题目在本张试卷中的分值”和“题目顺序”。exam_records 记录某学生参加某场考试的整体信息,answer_details 记录该学生每一道题的具体作答。

我强烈建议你在建表时就把外键逻辑写在注释里,但物理外键可以不加。用逻辑外键的好处是:数据导入导出时不怕外键约束卡壳;查询时用连接查询或应用层组装即可,性能可控。物理外键在并发写入量大时还会产生锁开销,考试系统这种中等压力场景用逻辑外键完全足够。

4.2 exam_records 与 answer_details 的建表思路

我直接给两个核心表的建表参考。考试记录表是整条业务线的关键表,它的字段决定了成绩统计好不好写。

CREATE TABLE `exam_records` ( `id` bigint NOT NULL AUTO_INCREMENT, `exam_id` bigint NOT NULL COMMENT '试卷ID', `user_id` bigint NOT NULL COMMENT '学生ID', `start_time` datetime DEFAULT NULL COMMENT '开始时间', `submit_time` datetime DEFAULT NULL COMMENT '提交时间', `objective_score` decimal(5,1) DEFAULT '0.0' COMMENT '客观题得分', `subjective_score` decimal(5,1) DEFAULT '0.0' COMMENT '主观题得分', `total_score` decimal(5,1) DEFAULT '0.0' COMMENT '总分', `status` tinyint DEFAULT '0' COMMENT '0考试中 1待批改 2已完成', PRIMARY KEY (`id`), UNIQUE KEY `uk_exam_user` (`exam_id`,`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试记录表';

注意这里exam_id + user_id的唯一索引,就是用来解决重复提交问题的。

答题详情表更细,一张记录对应学生在一次考试中的一题:

CREATE TABLE `answer_details` ( `id` bigint NOT NULL AUTO_INCREMENT, `record_id` bigint NOT NULL COMMENT '考试记录ID', `question_id` bigint NOT NULL COMMENT '题目ID', `question_type` tinyint DEFAULT '0' COMMENT '0单选 1多选 2判断 3简答', `user_answer` text COMMENT '学生作答', `correct_answer` text COMMENT '正确答案', `is_correct` tinyint DEFAULT NULL COMMENT '是否答对 0错 1对', `score` decimal(5,1) DEFAULT '0.0' COMMENT '本题得分', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='答题详情表';

这两张表设计好后,成绩统计就非常丝滑:按 record_id 聚合 detail,按 exam_id 和 user_id 分组算平均分,都能用简单的 SQL 完成。这里有个经验:answer_details不要存学生答案的文本镜像之外多余的东西,例如不要冗余存题目内容,因为题目可能被编辑,成绩是当时的快照,如果需要留存“考试时的题目快照”,建议单独用 JSON 字段存。

5. 关键代码实现与踩坑实录

5.1 JWT 拦截器的完整写法

后端拦截器是整个系统安全的核心,写的时候要考虑三个点:token 取出来之后是否合法、过期时间是否有效、这次请求的操作是否有权限。代码骨架大致是:

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } // 校验token,解析出 userId 和 role Long userId = JwtUtil.getUserId(token); String role = JwtUtil.getRole(token); if (userId == null) { response.setStatus(401); return false; } UserContext.set(userId, role); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }

在 SpringBoot 里注册拦截器时,要注意排除登录接口:

registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns("/api/auth/login", "/error", "/api/auth/register");

踩坑点注意:如果不排除 Swagger 文档和上传下载接口,联调时经常会被 401 卡住。如果前端上传文件用的是 multipart 请求,token 不能放在 body 里,必须放 header。还有,afterCompletion里清理 ThreadLocal 千万别忘,否则线程池复用线程时,下一次请求可能读到旧用户数据,这是很隐蔽的内存泄漏问题。

5.2 MyBatis-Plus 条件构造器与分页插件

MyBatis-Plus 的LambdaQueryWrapper是写业务查询的利器。比如教师后台要查“某个科目下难度为困难的选择题”,可以这样写:

LambdaQueryWrapper<Question> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Question::getSubjectId, subjectId) .eq(Question::getType, 0) .eq(Question::getDifficulty, 3); List<Question> list = questionMapper.selectList(wrapper);

LambdaQueryWrapper最大的好处是类型安全,字段名写错了编译期就报错,不会等到 SQL 执行才暴露。动态条件时用wrapper.eq(StringUtils.hasText(name), User::getName, name)这种重载,可以自动跳过空值条件,非常实用。

分页是考试系统列表页的刚需。MyBatis-Plus 分页需要配置一个分页插件:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }

分页查询时直接用Page<User> page = new Page<>(pageNum, pageSize)作为第一个参数传给 mapper 方法即可,返回的页对象里带了总数、总页数等属性。实际踩坑:不配置分页插件时,selectPage并不会报错,但分页不生效,它会查出全表然后内存分页,数据量一大就直接卡死。所以项目跑起来后,一定要先验证控制台打印的 SQL 里有没有LIMIT。

5.3 Vue3 前端状态管理与试卷提交逻辑

Vue3 考试页面的核心状态,我建议做法是:试卷结构存在一个ref里,用户答案存在一个reactive的 Map 对象里,倒计时单独用ref。用户切换题目,或者选择答案时,同步更新 Map:

<script setup> import { reactive, ref, computed } from 'vue' const paper = ref(null) const userAnswers = reactive({}) const currentIndex = ref(0) function handleAnswer(questionId, answer) { userAnswers[questionId] = answer } const answeredCount = computed(() => Object.keys(userAnswers).length) </script>

提交时,把userAnswers转成一个数组,连同examId、recordId一起 POST 给后端。这里最容易踩的坑是:用户在多选题里勾选答案后,需要把数组join(',')转成字符串再存,不然传到后端的数据结构是不一致的。还有,提交按钮要加防重复提交,最简单的方案是提交后把按钮 disabled,并加一个 loading 状态。

考试中断的处理也要考虑。如果学生做题中途刷新页面,答案是存在前端内存里的,刷新就没了。可靠的方案是每做一题,就把答案备份到 localStorage,只有提交成功后清除。或者更强一点:每次切换题目时调后端接口保存草稿。但这就引入草稿表和更多接口,课设阶段用 localStorage 就够了,但要在文档里说明这个限制。

5.4 联调时最容易踩的 5 个坑

前后端分离后,联调期基本每天都能遇到新问题。我把最常见的五个坑列出来,你遇到直接对照排查。

第一个是跨域。SpringBoot 里要配置 CORS,最简单的方式是写一个WebMvcConfigurer的跨域配置类,允许来源*、允许所有方法。不想每个方法都加注解,就在配置类统一处理。第二个是 Long 型 ID 精度丢失。后端主键是雪花 ID 或自增 Long,前端 JS 的 Number 只能安全表示到 2 的 53 次方,超过就会丢精度,导致列表最后几位变成 0。解决办法是让后端序列化 Long 为 String,要么加@JsonSerialize(using = ToStringSerializer.class),要么全局配置 Jackson 的 Long 转换规则。

第三个是时区和日期格式问题。后端返回Date给前端,经常出现差 8 小时或变成一串时间戳。统一做法是:后端序列化时指定yyyy-MM-dd HH:mm:ss,类型用LocalDateTime,数据库连接串配好serverTimezone=Asia/Shanghai。第四个是空字符串转枚举报错。比如前端传一个空字符串给后端枚举字段,Jackson 反序列化会直接 500。处理方式是在枚举字段上做空值转换,或者全局配置允许空字符串转 null。

第五个是前端代理配置。联调时前端不在服务器环境,很容易直接用 axios 请求后端http://localhost:8080,然后被跨域拦住。开发阶段最好用 Vite 的 server.proxy 配置代理,把/api转发到后端地址,这样浏览器里的请求都是同源的。到了生产环境再用 Nginx 做反向代理,原理是一样的。

6. 部署与运行环境配置

6.1 本地环境从头跑通的步骤

很多同学拿到源码后第一个卡点不是代码,而是环境。这里给一个从零到一的标准流程。

第一步装 JDK、Maven、MySQL8.0 和 Node。JDK 我建议用 8 或 11,Maven 用 3.6+,Node 用 16 或 18,版本不要太新,新版本有时跟旧依赖会有兼容性问题。MySQL8.0 安装完,一定要确认服务能启动,用命令行能登进去。

第二步是建库。执行项目里的 SQL 脚本,比如init.sql,把数据库、表和测试数据一次导入。导入完成后先查一遍关键表的数据条数。如果脚本执行报错,优先检查 MySQL 版本是否低于 8.0、编码集是否为 utf8mb4。

第三步改配置文件。application.yml里重点是数据库地址、用户名、密码和 Redis 配置。如果是本地单机环境,没有 Redis 就用不了缓存相关的东西,但很多基础版考试系统并不强制 Redis,直接把缓存配置注释掉即可。启动后端前,先确认 Maven 依赖能拉下来,国内网络建议配阿里云镜像,不然卡在下载依赖这一步能急死人。

第四步启动前端。进入前端目录,npm install安装依赖,然后npm run dev。这里常见问题是依赖版本冲突,如果项目锁定了版本,建议直接用npm ci代替npm install,能严格按 package-lock.json 安装。启动成功后,浏览器打开前端地址,先跑一遍登录、考试、提交的主流程,确认整个链路通不通。

6.2 服务器部署与 Nginx 反向代理

本地跑通不算完,考试系统最终是要给老师演示或真实使用的,部署到服务器是大概率需求。

后端打包用 Maven 的mvn clean package -DskipTests,生成 jar 包后传到服务器,用nohup java -jar xxx.jar --spring.profiles.active=prod > app.log 2>&1 &启动。如果服务器内存不大,可以加-Xms256m -Xmx512m限制堆内存。

前端先执行npm run build,生成 dist 目录,然后把它放到 Nginx 的 html 目录下。Nginx 配置里最关键的是 location 转发:

server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

这个配置里,try_files是 Vue Router 的 history 模式必需项,否则用户直接访问某个路由地址会 404,反而刷新就白屏。生产环境的 MySQL 建议单独开一台或至少配置个复杂密码,不要用项目默认账号密码裸露在公网,这个细节虽然老套,但真能帮你避免不必要的麻烦。

服务器部署还有一个容易被忽略的地方:防火墙和云服务器安全组。你本地怎么都访问不了,第一反应应该去检查 80 端口和 8080 端口有没有放行,而不是反复重装服务。另外,如果数据库是单独部署的,要允许后端应用服务器的 IP 访问 3306 端口,否则连不上库会一直报超时。

7. 项目文档里必须写清楚的内容

标题里带着【含文档】,这个文档其实是整套源码里很容易被忽略分量的部分。答辩的时候,老师的关注点往往不只是代码能不能跑,更在于你对系统设计思路的把握。项目文档至少要包含三块:需求文档、数据库设计文档、接口说明文档。

需求文档不要写一堆“系统是先进的”这种空话,而是写角色和流程。画清楚用例图,把每个角色的可操作用例列出来,再把核心流程用文字描述一遍。这部分能直接变成答辩 PPT 里的“需求分析”那页。

数据库设计文档要给出每一张表的字段说明,包括字段类型、含义、是否可空、默认值,以及表之间的关联关系。我习惯把建表脚本直接放进文档里,加一些文字注释。如果用了外键逻辑,一定要画一张简单的 ER 关系图,不需要用专业工具,PowerPoint 就能画。

接口说明文档可以用 Swagger 自动生成,代码里加好注解,启动项目后访问 swagger-ui 页面就能看到完整的接口列表。如果不想引入 Swagger,也可以把接口整理成 Markdown 表格,列出接口地址、请求方式、参数、返回结果。对课设项目来说,一份清晰的 Markdown 接口文档已经足够。

文档还有一个作用,是帮你“复习”项目。你隔一个月再回头看自己的代码,往往很多地方都忘了;但如果文档写得规范,你很快就能把项目逻辑捡回来,这对答辩前突击准备非常有利。

8. 二次开发的方向与建议

如果你拿到这套源码之后不是直接交差,而是想让它更有竞争力,有几个性价比很高的扩展方向。

第一个是支持 Excel 批量导入题库。教师手敲一道题一道题录入太低效了,做导入功能等于帮教师节省大量时间。后端用 EasyExcel 解析 Excel,前端提供模板下载和上传入口,导入时校验题型、分值、答案字段。这个功能做完,系统在老师心目中的评价会高出不少。

第二个是增加考试防舞弊措施。前端定时截图或统计失去焦点次数,记录到后端;考试结束后教师可以查看某学生的切屏记录。实现不算复杂,前端用visibilitychange事件,后端存日志表,但答辩时讲出来会很加分。

第三个是增加一个简单的系统公告模块。管理员发布考试通知,学生和教师在登录后可以看到最新公告。这是纯基础的 CRUD,锻炼价值有限但实用性强,适合给系统做内容填充。

第四个是数据可视化。用 ECharts 画出成绩分布图、班级对比图、题目正确率图。这些图表放在首页仪表盘上,整个系统的展示效果会立刻提升一个档次,而且 ECharts 是纯前端库,接入成本很低。

这些扩展方向其实都指向同一个判断:考试系统这类项目的核心竞争力不在于技术多复杂,而在于业务流程闭环是否完整、细节体验是否到位。你每多考虑一个真实使用场景,系统的完成度就高一分。

关于这套项目,最后想再啰嗦一句实际体会。我经手过不少课设和毕设项目,最常看到的失败原因不是技术难题,而是“版本不一致”。SpringBoot 是 2.3 的工程,代码里却用了 2.7 才有的写法;MyBatis-Plus 是 3.4 的版本,却按 3.5 的手册配置分页插件;前端的 Node 版本太高,导致 node-sass 安装失败。这些版本错位带来的问题,有时候比业务逻辑本身更恼火。所以拿到这套带着文档的源码,第一步一定是按文档里标注的版本号逐一对齐环境,再谈其他。版本对了,项目跑起来就是顺理成章的事;版本错了,再简单的代码也能把你卡得怀疑人生。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询