ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL在线考试系统实战:从数据库设计到部署全流程复盘

SpringBoot+Vue+MySQL在线考试系统实战:从数据库设计到部署全流程复盘 从零到一拆解一个 SpringBootVueMySQL 在线考试系统项目架构、数据库设计、论文与部署全流程复盘每年毕业季都会看到一堆人卡在在线考试系统这类题目上。说实话这个选题确实经典——它不像商城系统那样堆业务模块也不像管理系统那样偏纯增删改查而是有真实的状态流转、权限控制、时间约束和自动评分逻辑能把 SpringBoot、Vue、MySQL 这三件套的核心能力都覆盖到又不会复杂到毕设阶段做不完。所以我今天就把这套 SpringBootVueMySQL 在线考试系统的完整实现思路拆开来讲从功能设计、数据库建模到后端接口、前端页面再到论文结构和部署上线一条线捋清楚。这篇内容适合正在做同类毕设的同学也适合想系统了解前后端分离项目完整落地过程的初学者。我会把我常年在项目里验证过、也帮学生避过坑的那套做法直接摆出来包括表结构设计、核心接口逻辑、前端答题状态管理这些关键细节以及论文里该写什么、部署时该注意什么一次性讲透。1. 项目整体设计与功能模块拆解先把核心定位说清楚。在线考试系统的本质是把传统纸面考试的出题、组卷、答题、收卷、批改、成绩统计这几个环节全部线上化。虽然听起来不复杂但一旦落到系统设计上你就得同时处理三种不同角色的诉求——学生要顺畅答题、教师要灵活组卷和批改、管理员要维护基础数据和监控考试状态。1.1 角色权限体系与功能边界我见过不少起步阶段的同学一上来就急着写代码结果写到一半发现教师和管理员功能边界含混不清到处都在改。实际上这套系统只需要划分三种角色功能边界理清楚之后代码写起来基本不会返工管理员负责维护系统基础数据包括班级信息、学生账号管理、教师账号管理以及全局的考试数据统计。管理员不直接出题也不参与考试业务细节。教师核心业务角色负责题库维护单选、多选、判断题、试卷组卷策略配置、创建并发布考试以及考试结束后的手动批阅与成绩管理。学生参与考试的主体可以查看待参加考试、在线答题、自动提交并在教师公布成绩后查看自己的考试结果和错题解析。这个权限划分看起来很直白但实际编码时需要注意一个核心细节按钮级权限。后端接口除了登录校验必须在后端做前端页面也要根据角色动态渲染菜单和操作按钮否则只靠后端拦截器控制接口权限学生在页面上仍然能看到教师的操作入口体验上就会露怯。1.2 核心业务模块划分我把整个系统的功能模块划分为六大块这也是后面论文目录的核心骨架用户认证模块登录、注册、登出、密码加密存储、个人信息维护。题库管理模块题目的增删改查支持单选、多选、判断题三类题型题目关联知识点标签便于试卷随机组题。试卷组卷模块教师创建试卷定义试卷名称、总分、考试时长组卷方式支持固定选题和按规则随机抽取两种。在线考试模块学生进入考试倒计时、逐题作答、左侧答题卡实时同步、自动保存答案、倒计时结束自动交卷。自动评分模块客观题单选、判断由系统实时评分多选支持严格判分少选不得分和按比例给分少选按选项数量折算两种策略成绩自动写入数据库。成绩与统计模块学生查看成绩、试卷回看、错题解析教师查看班级成绩分布、导出成绩表。这六个模块是漏斗式递进的认证是所有模块的地基题库是试卷的数据来源试卷是考试的配置载体考试模块负责运行时流程评分是考试的结果处理统计是结果的展示出口。理解了这个依赖关系你会发现代码组织上其实是有天然顺序的不容易写乱。1.3 为什么选 SpringBoot Vue MySQL选题时技术栈不是随便定的SpringBoot Vue MySQL 这个组合能成为毕业设计主流背后是有实际原因的。后端用 SpringBoot是因为它把 Spring MVC、事务管理、数据访问这些基础能力都整合好了不用像 SSM 时代那套繁琐的 XML 配置启动一个内嵌 Tomcat 就能跑。更重要的是 SpringBoot 有非常成熟的生态——Spring Security 或 JWT 做认证、MyBatis-Plus 做数据层、Redis 做缓存和分布式锁都有现成的支持遇到问题时社区资料几乎能覆盖所有常见场景。前端用 Vue是因为它的生态和学习曲线刚好适合毕设周期。Vue 的单文件组件组织方式清晰Element UI / Element Plus 组件库能快速撑起后台管理系统的界面配合 Vue Router 管理路由、Axios 处理请求、状态管理用 Pinia 或者简单的 provide/inject一个完整的管理端页面开发效率极高。数据库选 MySQL理由更直接它足够稳定、资料海量、安装部署方便而且学生的课程几乎都接触过 MySQL 的 SQL 编写和表设计。在线考试系统的数据关系虽然不少但量级完全是 MySQL 的舒适区不需要引入更重的数据库中间件。2. 数据库设计五张核心表的建模思路在线考试系统能不能做好数据库设计占一半。表结构设计不合理后面要么写 SQL 写出痛苦面具要么频繁在 Java 代码里补各种逻辑来兜底。我把这套项目最核心的几张表拆开来逐一说直接给你能落到项目里的表结构。2.1 用户表用户表是最基础的表注意不要把管理员、教师、学生拆成三张表来存。三种角色统一放一张 user 表通过 role 字段区分1管理员2教师3学生这样可以大幅简化登录认证逻辑也便于未来扩展成更多角色比如监考老师。核心字段如下CREATE TABLE 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 tinyint(4) NOT NULL DEFAULT 3 COMMENT 角色1管理员 2教师 3学生, class_id bigint(20) DEFAULT NULL COMMENT 班级ID仅学生有值, email varchar(100) DEFAULT NULL, phone varchar(20) DEFAULT NULL, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1启用 0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里有几个细节值得注意。密码字段长度我留了100是给 BCrypt 加密算法预留的BCrypt 生成的结果是60位左右50的长度不够。username 加唯一索引是因为登录时按 username 查询走索引能避免全表扫描。role 字段用 tinyint 而不是字符串是为了查询效率同时配合代码里的枚举类去维护而不是散落一地的魔法值。class_id 关联班级时如果学校场景复杂也可以用单独的 class 表维护班级信息毕设阶段简单外键即可。2.2 题库表题库表的设计决定了组卷和后端评分逻辑的复杂度。我见过有人把每道题的选项存JSON字符串塞进一个字段里这样虽然插入方便但查询、展示、统计都很痛苦。正确做法是拆开question 表存题目的公共信息selection 表或选项表存每道题的选项列表。question 表核心字段如下CREATE TABLE question ( id bigint(20) NOT NULL AUTO_INCREMENT, type tinyint(4) NOT NULL COMMENT 题型1单选 2多选 3判断, content text NOT NULL COMMENT 题干内容, analysis text COMMENT 答案解析, difficulty tinyint(4) DEFAULT 1 COMMENT 难度1简单 2中等 3困难, knowledge_point varchar(100) DEFAULT NULL COMMENT 知识点标签, creator_id bigint(20) NOT NULL COMMENT 创建人ID教师, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_type (type), KEY idx_creator (creator_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT题目表;has_options 这个字段别加。很多设计喜欢加一个标记位表示判断题有没有选项实际上判断题可以统一为“选项A正确、选项B错误”的方式来处理题型一致性对编码和解码都有好处。判断和单选的选项结构完全一致多选只是答案数量多于一个而已。这里有一个关键的建模决策答案存哪里我建议把正确答案也放到 question 表里用两个字段表示answer标准答案和 answer_detail用于多选的按比例给分策略。对于单选和判断answer 就是正确选项的编号对于多选answer 就是正确选项的集合字符串比如“1,3,5”。判断逻辑在后端统一做这样才能在并发提交时保证数据一致性。2.3 试卷表与组卷表试卷表是考试配置的载体设计相对直接CREATE TABLE exam ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_name varchar(100) NOT NULL COMMENT 考试名称, creator_id bigint(20) NOT NULL COMMENT 创建人教师, exam_time_limit int(11) NOT NULL DEFAULT 60 COMMENT 考试时长分钟, total_score int(11) NOT NULL DEFAULT 100 COMMENT 试卷总分, start_time datetime DEFAULT NULL COMMENT 考试开始时间, end_time datetime DEFAULT NULL COMMENT 考试结束时间, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0未发布 1进行中 2已结束, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试表;exam_question 关联表用于固定组卷要存储题目顺序、每题分值以及随机组卷模式下的随机规则快照。注意“快照”这个概念后面会细讲。CREATE TABLE exam_question ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_id bigint(20) NOT NULL, question_id bigint(20) NOT NULL, sort_order int(11) NOT NULL DEFAULT 0 COMMENT 题目顺序, score int(11) NOT NULL DEFAULT 2 COMMENT 该题分值, PRIMARY KEY (id), KEY idx_exam (exam_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试题目关联表;为什么需要快照这是在线考试系统和普通管理系统的最大区别之一。教师创建一场考试后题目可能被修改甚至被删除。但如果考试已经发布学生答卷时的数据必须保持当时的题目状态否则会出现“题目已经改了但成绩单还对不上”的严重数据事故。解决方式有两种一是考试发布后把题目复制一份到考试题表二是在考试题表里加一个 exam_question 的题目内容冗余字段。推荐用第二种的简化版——考试题目关联表里同时保存当时的题干快照和选项快照这样即使原 question 表被改动考试回看和成绩统计依然正确。2.4 考试记录表与答题明细表这两个表与考试过程强相关也最容易被忽略。CREATE TABLE exam_record ( id bigint(20) NOT NULL AUTO_INCREMENT, exam_id bigint(20) NOT NULL COMMENT 考试ID, student_id bigint(20) NOT NULL COMMENT 学生ID, start_time datetime DEFAULT NULL, submit_time datetime DEFAULT NULL, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 答卷状态0未开始 1进行中 2已提交 3已评阅, score decimal(5,1) DEFAULT NULL COMMENT 最终得分, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_exam_student (exam_id, student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试记录表;exam_record 是学生参与考试的唯一入口记录每名学生在一场考试中只有一条记录unique 约束就是防重复入场。它同时承担着保存答卷状态的作用从“未开始”到“进行中”到“已提交”再到“已评阅”整个生命周期都靠 status 字段流转。答题明细表是逐题答案的存储表CREATE TABLE exam_answer ( id bigint(20) NOT NULL AUTO_INCREMENT, record_id bigint(20) NOT NULL COMMENT 考试记录ID, question_id bigint(20) NOT NULL COMMENT 题目ID, student_answer varchar(255) DEFAULT NULL COMMENT 学生作答内容, correct_answer varchar(255) DEFAULT NULL COMMENT 标准答案快照, is_correct tinyint(4) NOT NULL DEFAULT 0 COMMENT 是否正确1正确 0错误, score decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT 该题得分, PRIMARY KEY (id), KEY idx_record (record_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT答卷明细表;correct_answer 这个字段就是快照思想的又一体现。评分完成后再去 question 表查正确答案如果题目被删除成绩记录就崩溃了。把答案快照冗余进来成绩查询永远独立于题库状态。2.5 索引设计与常见设计坑主键和常用的查询外键一定要建索引比如 exam_answer 的 record_id 和 question 表的 type、creator_id。还要注意 MySQL 外键约束我建议在物理层面不要强依赖外键约束保持逻辑外键即可。原因很简单用外键约束会导致数据插入删除时的锁竞争而且一旦题目被改动、考试历史记录需要保留外键反而成了枷锁。逻辑外键配合事务数据一致性和灵活性都能兼顾。另一个常见坑是日期时间字段的类型。考试开始时间、结束时间、提交时间建议直接用 datetime而不要用 timestamp。timestamp 范围到2038年虽然最近还远但毕设答辩时评委偶尔会问这个问题直接用 datetime 一劳永逸。更重要的是所有的起止时间判断统一用后端服务器时间不要依赖前端传入的时间前端时钟是可以被改的。3. SpringBoot 后端核心实现与避坑细节后端是整套系统的逻辑大脑。学到 SpringBoot 阶段大家都能写 CRUD但考试系统真正的难点在状态控制、自动评分和权限校验这几个点上。我把这几块的实现思路完整过一遍。3.1 项目结构与分层我习惯的分层是标准的 Controller-Service-Mapper 三层加上一个 config 包和一个 common 包。对于毕设规模不必过度拆分但至少要保证 Controller 不写业务逻辑、Service 事务边界清晰、Mapper 只做数据访问。项目结构大概是src/main/java/com/example/exam/ ├── config // 跨域配置、MyBatis-Plus 配置、JWT 拦截器配置 ├── controller // 用户、题库、试卷、考试、成绩等接口层 ├── service // 业务逻辑层接口实现 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体类 ├── dto // 前端请求/响应对象避免直接暴露实体 ├── common // 统一返回类、异常处理、常量枚举 └── util // JWT 工具类等这里有个建议DTO 层。很多同学喜欢直接把 Entity 返回给前端图省事但一旦前端需要的字段和后端实体字段不一致就会被迫写一堆 JsonIgnore 或者在实体里塞非表字段慢慢腐化。用 DTO 接收前端参数、用 VO 返回视图数据前期多写几个类后期改接口时你会感谢这个决定。3.2 JWT 登录认证在线考试系统的登录认证我用 JWT 而不是传统 Session原因有两个一是前后端分离部署时Session 要处理跨域 Cookie 携带问题比较麻烦二是 JWT 无状态后端服务重启后用户不用重新登录对考试这种长流程场景更友好。核心实现是写一个拦截器或者 Spring MVC 的 HandlerInterceptor拦截所有需要登录的接口解析请求头里的 Authorization 字段校验 JWT 合法并且未过期然后把用户信息存入 ThreadLocal 供后续业务使用。public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new AuthException(未登录); } token token.substring(7); Long userId JwtUtil.parseToken(token); if (userId null) { throw new AuthException(登录已过期); } UserContext.set(userId); return true; } }需要特别提醒的一点是 JWT 密钥不要硬编码在代码里尤其不要写“123456”这类密钥虽然毕设答辩一般不会黑你的系统但作为习惯还是要把它放到 application.yml 配置文件中并且使用足够长的随机字符串作为签名密钥。另外学生和教师两种角色共用一个拦截器即可但要通过注解或者路径规则区分接口权限比如 /api/student/** 只能学生访问/api/teacher/** 只能教师访问在拦截器里对路径前缀做角色判断即可。3.3 考试发起与题目快照考试发布是整个系统的核心接口。学生端进入考试时后端接口要做的事情按顺序来校验考试是否在有效时间窗口内。校验当前用户是学生且未被禁用。在 exam_record 表中查到或创建考试记录。根据 exam_id 查询 exam_question 表得到该学生本次考试的试题列表。返回给前端的数据中题目选项和分值来自快照字段而不是实时查 question 表。从 security 角度说学生请求某个考试详情时如果考试还没到开始时间或者已经结束后端必须返回明确的错误码而不是返回题目列表让前端去判断。这个校验一定在后端做前端只是辅助展示。还有一点同一名学生能否多次进入考试答案是可以前提是之前没有提交。比如学生做题做了一半关掉浏览器重新登录后进入考试应该能够看到自己之前保存的答案而不是重新开始。这个功能依赖 exam_answer 表中已保存的答案数据。所以在线考试中“保存答案”这个动作必须有每次切题或点击下一题时前端把当前题目的答案存入后端的临时保存接口。3.4 自动评分逻辑自动评分只针对客观题。后端评分比前端评分可靠得多不能相信浏览器传过来的得分所有得分必须在后端根据标准答案重新计算。遍历 exam_answer 表逐题评分纯 Java 运算即可不需要什么高级框架。private double calculateQuestionScore(StudentAnswer answer, Question question) { int questionType question.getType(); if (questionType 1 || questionType 3) { // 单选、判断完全匹配 return answer.getStudentAnswer().equals(question.getCorrectAnswer()) ? question.getScore() : 0; } else if (questionType 2) { // 多选两种策略 ListString studentSelected parseOptions(answer.getStudentAnswer()); ListString correctOptionList parseOptions(question.getCorrectAnswer()); if (isExactMatch(studentSelected, correctOptionList)) { return question.getScore(); } if (isSubsetMatch(studentSelected, correctOptionList)) { // 少选不选按比例给分 int correctCount 0; for (String opt : studentSelected) { if (correctOptionList.contains(opt)) { correctCount; } } return question.getScore() * correctCount / correctOptionList.size(); } return 0; } return 0; }注意多选策略的选择必须在考试发布时确定不能全局统一写死。有的老师要求多选错选不得分少选给部分分有的老师要求多选只要有一项错就零分。这两种策略都需要在试卷配置里加一个字段比如 strict_score 标记位评分接口按不同策略执行。3.5 事务与并发处理考试提交接口是整个系统中并发风险最高的地方因为考试时间到后大量学生同时点击交卷。这里的关键规避方式有两点第一点是防重复提交。用 exam_record 表的唯一索引exam_id, student_id来兜底提交前先 UPDATE ... SET status 2 WHERE id ? AND status 1如果影响行数为0说明已经被提交过或者成绩状态不正确直接拒绝即可。这个 CAS 思路比先查再写要可靠得多也不会被打到 DB 前端的防重复点击插件干扰。第二点是事务边界。auto_submit 自动交卷接口和 submit_exam 手动交卷接口都标 Transactional防止评分写到一半数据库出错产生半截数据。事务中加一个 Redis 分布式锁或者直接用数据库记录的行级锁也可以毕设阶段用数据库行级锁SELECT ... FOR UPDATE配合唯一索引就能解决大多数并发问题。3.6 定时任务处理考试过期学生考试到时间未点击提交怎么办两个方案一是后端提供一个当前时间超过考试结束时间就自动提交的检查接口二是用 Spring 的 Scheduled 写一个定时任务每分钟扫一遍 exam_record 表把 status1 且截止时间早于当前时间的记录自动提交。我建议毕设阶段用第二种方案并且在论文里写清楚“通过定时任务自动结算超时未交卷的答卷”这是一个加分点。需要注意定时任务的事务传播、每次扫描的数量限制避免大批量数据一次性提交导致数据库压力抖动。你可以做成每次扫描 100 条、批处理的方式。4. Vue 前端页面结构与关键交互实现前端部分很多人低估了工作量。后端 CRUD 好写但考试答题页面的倒计时、答题卡、自动保存这些交互如果实现得粗糙体验会显得不太完整答辩时也可能被追问。我把前端结构和一个关键细节讲清楚。4.1 前端工程结构与路由设计前端用 Vue 3 Element Plus Vue Router Pinia Axios 是比较主流的选择。如果是要维护 Vue 2 项目则对应 Element UI。三套角色使用的页面是混在一个前端工程里的根据 UserInfo.role 动态渲染菜单。src/ ├── api/ // 按模块封装的 axios 请求 ├── router/ // 路由配置带前置守卫 ├── store/ // Pinia 状态管理 ├── layout/ // 主布局侧边栏 顶栏 ├── views/ │ ├── login/ │ ├── admin/ │ ├── teacher/ │ │ ├── question-manage/ │ │ ├── exam-manage/ │ │ └── score-manage/ │ └── student/ │ ├── exam-list/ │ ├── exam-taking/ // 考试答题页 │ └── exam-result/路由守卫是前端权限控制的第一道门槛。核心逻辑是每次路由跳转前判断有没有 token没有就强制跳登录页有 token 再根据路由 meta 里配置的角色判断当前用户是否有权限进入没有权限就跳回首页。虽然后端接口还有一层校验但前端路由守卫能极大提升用户体验避免用户点开一个页面后才弹出“无权限”报错。4.2 考试答题页的设计要点考试答题页是前端最复杂的单页我拆成几个关键点来讲。答题区设计和答题卡要联动。学生点击左侧答题卡上的题目编号时路由参数保存当前题的序号可以只把当前题目数据放到状态里用 Pinia 的 store 维护当前题号、每题答案、剩余时间等状态。这里有一个比较实用的设计题目列表一次性加载答案也一次性加载或至少把已保存答案一次性加载切题时不发请求只是本地状态切换只有点击“保存下一题”或改变答案并失焦时才触发自动保存。倒计时是用 setInterval 实现的初始化时拉取服务器时间然后不断减秒。注意倒计时结束时即使前端没有触发提交后端也要能兜底——因为在定时任务里已经扫描超时记录并自动提交了。前端倒计时和数据库的超时结算相互配合而不是相互依赖。自动保存的防抖处理也提醒一下。学生每改一道题的答案就立刻发一次请求考虑极端情况会产生大量请求。我给的建议是答题过程中每 10 秒检查一次是否有未同步的答案变更有则批量提交切题和交卷时强制同步。这样既保证数据安全性又不会把后端接口打穿。Vue 里来实现这个逻辑很简单但你必须在组合式 API 里把 setInterval 和 beforeunload 事件处理干净否则跳路由后定时器还在跑会引发内存泄漏和重复保存。4.3 数据回显与成绩单考试提交后学生端查看成绩单应该展示每道题的题干、自己的答案、正确答案、是否正确、得分、解析。这个页面的数据来自后端返回的已评阅答卷快照前端不需要任何计算逻辑。也就是说教师批阅完成的答卷数据是后端组装的前端只是展示。这里引出一个实现细节成绩列表需要支持“查看答题卡”“查看具体题目的学生答案和正确答案对比”这类数据在后端组装 VO 时就要结构清晰{ questionId: 12, questionType: 2, content: 以下哪些属于面向对象特性, options: [封装, 继承, 多态, 递归], studentAnswer: 1,2,3, correctAnswer: 1,2,3, isCorrect: true, score: 5, analysis: 递归不是面向对象的特性 }前端拿到这个结构直接渲染即可不需要知道判断逻辑也正因如此后端评分策略如果变了前端完全不用改。5. 论文撰写结构与部署实操很多人觉得把这个系统写出来已经够辛苦了但论文在毕业设计中占比同样很高。论文不是代码的流水账而是要突出“需求分析—系统设计—系统实现—系统测试”这条主线。我把论文框架和部署文档里必须有的内容逐一列清楚。5.1 论文框架推荐使用下面这个结构这也是答辩老师最熟悉的套路绪论课题背景与研究意义、国内外现状、论文组织结构。相关技术介绍SpringBoot 框架、Vue 框架、MySQL 数据库、JWT 认证、前后端分离架构。技术介绍不要长篇抄书每项技术 300 字以内重点说明它在本系统中承担什么作用。系统需求分析角色分析、功能需求分析用文字描述功能点、非功能需求分析安全性、稳定性、响应时间。系统设计总体架构图前端、后端、数据库三层、功能模块设计、数据库设计ER 图 主要表结构描述、接口设计。系统实现按模块分小节每个模块写清楚实现思路、关键代码片段和界面截图。建议贴代码时只贴核心片段不要整段 Service 类贴上去每个代码片段都要配一段解释。系统测试功能测试用例表 测试结果 部分性能或者兼容性测试结果。总结与展望完成的工作、遇到的问题及解决、不足之处与后续改进方向。论文里可以画结构图但手写工具或者用 draw.io 画就好。答辩时需要解释清楚为什么选择前后端分离为什么用 JWT 而不是 Session数据库表为什么这么设计——这些问题在我前面讲的内容里都有答案。5.2 部署文档写什么这套项目的部署文档通常需要覆盖两种场景本地开发环境和服务器生产环境。我自己写部署文档时会按照下面这个清单来组织本地环境部署JDK 版本说明JDK 8 或 JDK 11 均可不推荐 JDK 17 跑老版本 Spring Boot 2.x。MySQL 版本要求5.7 或 8.0创建数据库 exam_system设置 utf8mb4 编码。后端配置修改 application.yml 里的数据库连接信息、Redis 地址、JWT 密钥。启动方式IDEA 启动 SpringBootApplication 主类后端默认端口 8080。前端配置修改 .env.development 里的 VUE_APP_BASE_URL 指向后端地址。前端启动方式npm install 安装依赖npm run serve 启动开发服务器默认端口 8081。生产环境部署前端执行 npm run build生成 dist 目录使用 Nginx 托管 dist 静态文件。后端执行 mvn clean package 打成 jar 包使用 java -jar 启动或者通过 systemd 守护进程管理。Nginx 配置反向代理/api 路径转发到后端地址前端静态资源由 Nginx 直接提供服务。数据库使用 MySQL 8.0 生产实例执行项目附带的 exam_system.sql 初始化脚本。服务器防火墙开放端口域名如果配置了 HTTPS证书路径要在 Nginx 里正确引用。提示部署文档的核心价值在于“照着做能跑通”所以每一步都要写到具体的命令和文件路径级别不要只写“配置好数据库”这种话。凡是你能想到初始化数据、测试账号、常见启动报错排查都往里放。5.3 常见部署报错的排查思路这部分内容我建议也写进部署文档的附录因为实操中一定会遇到。我把我自己踩过的和帮人排过的几个典型问题列出来了前端 npm install 失败多为 Node 版本与依赖版本不兼容。Vue 3 要求 Node 14.18npm 7Vue 2 用 Node 12 到 14 比较稳。解决方式是 nvm 切换 Node 版本后重装依赖。Nginx 刷新页面 404这是前端路由 history 模式导致的需要在 Nginx 配置文件里加 try_files 指令location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }MySQL 8 的驱动依赖引入默认用 com.mysql.cj.jdbc.Driver不要写成老版本驱动类名否则会直接报 ClassNotFoundException 或时区异常。后端启动后端口被占用在 application.yml 里改 server.port或者启动时指定 --server.port8081。跨域请求报 403 / REDACTED后端配置跨域过滤器同时前端 devServer 里配置 proxy 代理。生产环境用 Nginx 代理时同源访问不存在跨域问题所以开发环境代理和生产环境反向代理要区分。6. 从实际项目复盘出的常见问题与解决建议代码能跑通只是第一步真正提升项目完成度的是对细节的把控。这一部分我重点整理常见问题也把答辩可能会问到的点放进来。6.1 功能层面的易错点考试成绩什么时候允许查看如果教师还没批阅完主观题学生成绩单不能出现成绩字段。所以成绩查询接口要判断 exam_record.status 是否为 3已评阅否则只返回“阅卷中”。试卷发布后还能改题目吗我的建议是发布后题目变更要回到 draft 状态才允许改或者干脆锁定期内不能改否则会导致已经创建答卷的学生看到题目前后不一致。定时任务自动提交和手动提交不能互撞。手动提交时如果定时任务正好在跑可能造成重复提交。因此在提交接口里要用前面说的 CAS 更新方式同时状态字段加版本号或者乐观锁。教室考试和高并发如果是全校同时考试同一时刻几十个请求同时进来除了数据库事务还要考虑 Nginx 连接池、Tomcat 最大连接数、数据库 max_connections 这几个配置参数的调整。毕设一般不会压到瓶颈但答辩老师如果问起来至少能说清楚怎么调。6.2 安全层面的易错点密码不能明文存。Spring Security 的 BCryptPasswordEncoder 或者 jBCrypt 都可以禁止用 MD5 加盐这种已被证实不安全的方案。接口权限不能只防君子。学生调教师接口修改成绩这种如果后端没有角色校验就是严重安全漏洞。我建议在拦截器中用注解如 RequireRole(role RoleEnum.TEACHER)来控制接口权限。考试答案不能从前端传回。评分的重要性前面讲了但要再强调一次前端传给后端的只能是自己选的答案不能是“得分”。后端评分并校验答案结构、选项范围合法。6.3 答辩追问预判在线考试系统做得人很多答辩老师问的问题也大多集中在那几个点上。我建议提前准备以下问题的回答为什么用 JWT 而不用 Session答前后端分离架构 横向扩展友好 服务端无状态。如何防止学生考试时切换页面查答案答这在目前系统里属于非功能需求可以通过检测页面可见性 API 和考试时间内的页面埋点日志来辅助提醒但严格防作弊需要更复杂的锁屏和摄像头监控不在本系统范围内。多选评分规则为什么由后端控制答前端可以被篡改评分必须以服务端计算结果为准。考试中途断网怎么办答答题状态实时保存在服务端断线重连后重新进入考试页面可以恢复历史答题数据。系统能支撑多少并发答单机部署下 Tomcat 默认 200 线程池MySQL 连接池合理配置可支撑中小规模考试的并发需求如需更高并发可以引入 Nginx 负载均衡和 Redis 缓存优化。6.4 我个人的一点实操体会做完这套在线考试系统最大的感受是这个题目“看起来简单做起来碎”。碎片主要来自几个地方——状态流转特别多考试有未发布、进行中、已结束答卷有未开始、进行中、已提交、已评阅权限边界要理清三种角色三种菜单接口要防住前端答题页交互复杂倒计时、答题卡、自动保存必须联动还有论文与代码要互相对应。任何一个环节做得糙项目就显得松散。如果时间紧张我建议实现优先级按这个顺序来卡先把登录认证和题库管理跑通然后是试卷创建和考试发布再是学生答题和自动交卷最后是成绩查询和统计导出。前面两个模块是地基中间两个模块是核心功能最后一个模块是锦上添花。只要这四个阶段的任务都完成了系统的完整性已经足够支撑毕业设计答辩。另外源码里记得写好 README把测试账号、数据库初始化方式、运行步骤、默认端口写清楚。部署文档和论文里的系统测试章节也是互通的你把测试用例表整理好可以直接挪进论文第六章性价比很高。答辩时不要只会说“我调通了”要能讲清楚每个设计背后的约束和取舍——这才是评委想听到的。
返回列表