
写毕设最怕的不是功能做不出来而是做出来之后说不清楚“为什么这么设计”。考试系统这个题目在Java Web方向的毕业设计里属于常青树——它业务闭环清晰有用户、有状态流转、有权限控制、有并发场景既能展示后端功底又能通过前端交互体现完整度。我这次把一个完整的SpringBootVue考试系统从数据库脚本到接口联调整个捋了一遍发现真正花时间的不是写代码而是把模块边界划清楚、把接口约定定明白、把SQL脚本设计得能支撑业务扩展。这篇文章我按实际动手的顺序来写从架构设计思路到核心功能实现从初始化项目到对接联调最后整理一份排查问题和答辩准备的速查清单。不管你是打算直接参考这套源码还是想自己从零写一个类似的系统这篇文章都能给你一条走得通的路线。1. 项目架构设计与技术选型思路1.1 为什么是SpringBootVue这个组合先说技术选型。SpringBoot在当前Java Web生态里的地位不用多讲它解决了Spring MVC时代大量XML配置的痛点内嵌Tomcat容器打包成可执行的Jar包就能跑这对毕设来说意味着什么意味着你在答辩现场可以非常从容地演示一个java -jar命令启动后端一个npm run dev启动前端整个过程不需要额外装Tomcat或者配置外部依赖演示环境的搭建成本非常低。Vue作为前端框架核心优势在于组件化和响应式数据绑定。考试系统里有大量“列表展示——点击进入——填写作答——提交评分”的交互模式用Vue的组件化思路来组织页面每个功能区块可以独立维护代码的可读性和答辩时的讲解清晰度都会好很多。尤其是用到Element UI这类组件库之后表格、表单、弹窗、分页这些后台管理系统的高频组件都是现成的你不用从零去写一个日历控件或者下拉框。前后端分离还有一个隐性好处答辩时你可以把端口改成不同的前端走8080后端走8081然后跟评委解释“前端通过axios调用后端接口双方只通过JSON交换数据”。这句话虽然简单但能直接体现你对现代Web开发模式的理解比堆砌一堆框架名称要有说服力。1.2 整体模块划分与功能边界考试系统平台按角色可以拆成三个端管理员端、教师端、学生端。但这三个端在后端实现上不需要拆成三个独立项目而是通过在用户实体上设计角色字段配合接口层面的权限控制来处理。后端模块我建议这么划系统管理模块用户管理、角色管理、菜单权限题库管理模块题目CRUD、题目分类、题目导入导出试卷管理模块组卷规则、试卷生成、试卷发布考试过程模块在线考试、自动计时、答案暂存、自动交卷阅卷评分模块客观题自动判分、主观题人工评分、成绩统计数据统计模块考试成绩分布、及格率、题目正确率前端页面按照功能域组织路由规划上采用Layout嵌套结构/login登录页三个角色共用/admin用户管理和数据统计/teacher题库管理、试卷管理、人工阅卷/student考试列表、在线答题、成绩查看模块边界划清楚之后开发顺序就出来了先做登录认证再做题库和试卷然后做考试流程最后做统计。考试流程是整个系统的核心链路建议单拆出来仔细设计状态流转。一个学生从进入考试到交卷中间要经历“未开始、考试中、已交卷、已批阅”四个状态这四个状态分别对应后端接口的什么逻辑在动手写代码之前最好用文字列一遍。1.3 数据库设计写SQL脚本前先想清楚这五张核心表数据库设计是这套源码里SQL脚本部分的核心价值所在。考试系统的表结构有固定套路但很多人第一次写容易漏掉关键表或者表间关系没理清。我认为至少要有这五张核心表用户表sys_user字段名类型说明idbigint主键用户IDusernamevarchar(50)登录账号passwordvarchar(100)BCrypt加密后的密码roletinyint1管理员 2教师 3学生real_namevarchar(50)真实姓名statustinyint是否启用题目表exam_question字段名类型说明idbigint主键题目IDtypetinyint1单选 2多选 3判断 4简答subjectvarchar(200)题干内容optionstext选项JSON如[A,B,C,D]answervarchar(500)标准答案scoreint每题分值analysistext答案解析试卷表exam_paper字段名类型说明idbigint主键试卷IDtitlevarchar(100)试卷名称durationint考试时长分钟total_scoreint总分statustinyint0草稿 1发布 2已结束created_bybigint创建教师ID考试记录表exam_record字段名类型说明idbigint主键记录IDpaper_idbigint试卷IDstudent_idbigint学生IDstart_timedatetime开始时间submit_timedatetime交卷时间scoreint总得分statustinyint0未开始 1考试中 2已交卷 3已批阅答题明细表exam_answer_detail字段名类型说明idbigint主键明细IDrecord_idbigint考试记录IDquestion_idbigint题目IDstudent_answertext学生答案scoreint本题得分为什么答案明细要单独拆一张表因为评分是分题目进行的客观题提交时就能自动判主观题需要教师人工阅卷拆开之后单题的评分状态和得分就可以独立管理也方便后续做题目正确率分析时直接通过question_id聚合。这里有一个很多人会忽略的细节题目表的answer字段建议存成统一的字符串格式不要有的存A、有的存“正确”、有的存“对”。选项题统一存字母判断题统一存“T/F”或“正确/错误”在自动判分代码里就只需要写一套比较逻辑。这种细节做得好调试和扩展都会舒服很多。2. 核心功能实现考试业务的关键链路2.1 登录认证与权限控制登录认证这块毕设项目里最常用的方案是JWTJSON Web Token Spring Security或者JWT 拦截器。考虑到完整源码的受众大部分是学生我建议用JWT 拦截器的轻量方案核心逻辑好讲代码量也少但能覆盖到权限校验的主要知识点。JWT的结构分为三部分Header头部、Payload载荷、Signature签名。服务端在用户登录成功后生成一个Token返回给前端前端后续请求在Authorization请求头里带上这个Token后端写一个拦截器统一解析校验。Token里可以存用户ID、用户名、角色这些不敏感的信息但绝不能把密码放进去。这里需要补充一个理解扩张点JWT是无状态的服务端不存Token每次请求通过签名算法验证Token是否被篡改。这跟Session方案最大的区别在于Session存在服务端内存或Redis里JWT存在客户端。考试系统这种场景用JWT比较舒服因为学生交卷、刷新页面、重新打开浏览器时不需要重新登录Token的有效期可以设置成2小时甚至更长正好覆盖一场考试的时长。权限控制的粒度不需要做到按钮级别按角色控制到接口和菜单就够用了。后端拦截器里校验角色的写法大概是// 判断角色是否满足接口要求 if (tokenUser.getRole() ! 1 !allowRoles.contains(tokenUser.getRole())) { throw new BusinessException(无权访问); }前端Vue侧配合vue-router的全局前置守卫做路由拦截未登录跳转登录页已登录但角色不匹配的跳转403页面。这套逻辑虽然简单但前有后端接口校验、后有前端路由守卫属于完整的双层防护答辩时有得说。2.2 在线考试主流程的状态设计在线考试这块最容易出bug的地方不是业务代码本身而是状态管理。学生打开试卷、中途刷新页面、意外关闭浏览器、到时间自动交卷这些异常情况都得兜住。我建议把状态机直接在代码里写清楚每个状态之间合法的转换路径只有一条。具体来说学生点击“开始考试”时后端创建一个考试记录exam_record状态为“考试中”并记录start_time。考试过程中前端每答题一题就调用一次“暂存答案”接口把答案写进exam_answer_detail表这样即使学生中途刷新页面已经答过的题目也不会丢。交卷动作分两种学生主动交卷以及考试时间到达后前端倒计时到0自动触发交卷接口后端还要做一个兜底校验即使前端没有触发当查询考试成绩时发现start_time duration小于当前时间就把记录置为“已交卷”并对已答题目进行自动判分。自动判分逻辑建议放在一个独立的方法里用事务包裹。核心流程是遍历该考试记录的所有答题明细判断题目类型——单选、判断这些客观题直接比对标准答案多选稍微特殊如果批改规则是”完全一致才给分“那就比对集合是否完全相同如果你想把评分容忍度做大一点也可以设计成少选给一半分这部分属于业务规则没有绝对的对错但要在论文里说清楚你采用的是哪种规则。简答题不参与自动判分置为待阅卷状态统一由教师端在阅卷列表里手动打分。有一个坑提一下前端倒计时不要只做减法要以后端返回的start_time和duration为准计算剩余时间。原因很简单学生可能考试途中网络断了一下或者切换了一下页面导致setInterval被浏览器节流如果前端死守本地的倒计时刷新之后就跟真实时间对不上了。每次刷新或者暂存答案时重新拉取一次服务端时间戳才能保证倒计时的准确性。2.3 组卷策略从题库随机抽题到试卷结构组卷功能听起来高大上本质上就是一套抽题规则。比较常见的两种组卷方式手动选题和智能组卷。手动选题就是教师从题库里一道一道挑适合小规模试卷智能组卷是按规则自动生成核心参数有题目类型、各类型数量、各类型分值、难度系数。这套源码里如果实现了智能组卷基本就是下面这类流程// 抽取单选题从指定分类中随机取N道 SELECT * FROM exam_question WHERE type 1 AND category_id ? ORDER BY RAND() LIMIT ?这里有一个容易被忽略的点ORDER BY RAND()在数据量小的时候没有问题如果题库有上万条数据性能会明显下降。毕设答辩时如果评委问到这个你可以回答“通过先查询符合条件的题目ID列表再用random选取其中的ID进行二次查询”展示你考虑过规模化场景但不建议真的把代码改得很复杂因为考试系统的数据量在毕设场景下到不了那个规模。整卷生成之后要校验总分是否等于试卷设定总分。比如单选20题每题2分多选10题每题3分判断10题每题1分简答4题每题5分总分是多少、跟试卷表里total_score字段对不对得上代码里必须有这样一道校验逻辑避免出现“试卷显示100分实际题目加起来105分”这种低级错误。2.4 前端核心页面与Vue组件设计前端是很多毕设容易翻车的地方。考试系统的页面不算多但交互细节不少。Vue组件设计上建议按照“页面——区块组件——基础组件”三层来组织。我按自己的经验拆一下关键页面登录页表单校验做好回车键触发登录登录后按角色跳转不同首页考试列表页学生端展示可参加的考试、已结束的考试状态用Tag标签区分未开始的可点击“进入考试”、已交卷的可点击“查看成绩”在线答题页“顶部固定倒计时 左侧题目列表 右侧答题区”的三栏布局是最经典的考试界面设计。左侧题目列表点击可以跳题已经作答的题目要高亮点击交卷时如果还有未答题目需要二次确认阅卷页教师端左侧学生列表右侧答题内容展示主观题每个题目一个打分输入框支持批量提交成绩组件抽取上答题页里的“倒计时组件”值得单拆出来因为它有独立的生命周期开始、暂停、结束也可以被复用。选项组件也建议抽出来因为单选题、多选题的交互逻辑不同但展示结构类似多选用复选框、单选用单选框判断就是两个选项的特例。路由配置上Vue Router用动态路由或者静态路由都可以。这个项目建议用静态路由配合角色判断来控制菜单显示因为角色是固定的三种不需要在运行时动态生成路由静态实现的维护成本更低。热词里反复出现“vue动态路由”但在考试系统这个场景静态路由加权限判断是更务实的选择答辩时如果你能主动解释这个取舍逻辑比盲目用动态路由更能体现你的判断力。3. 从零复现项目初始化到部署全流程3.1 后端SpringBoot项目初始化SpringBoot项目的搭建现在非常方便推荐直接去Spring Initializr官网生成基础工程版本选择SpringBoot 2.7.x系列。选择2.7而不是3.x的原因很实际3.x要求JDK17起步很多学校的课程环境还停留在JDK82.7.x兼容性更好且网上资料最多遇到问题更容易搜到解决方案。依赖选择上在Initializr里勾选这几个即可Spring Web提供Web能力MyBatis Framework数据库访问MySQL Driver数据库驱动Lombok简化实体类代码注意Initializr默认勾选的可能包含一些用不到的依赖比如Security或者DevTools。DevTools虽然可以做热部署但在毕设阶段经常会因为类重载导致一些莫名其妙的缓存问题建议不勾选开发时改代码重启一下也不费事。启动类结构保持标准的SpringBoot三段式SpringBootApplication MapperScan(com.example.exam.mapper) public class ExamApplication { public static void main(String[] args) { SpringApplication.run(ExamApplication.class, args); } }统一返回结果的包装类建议在一开始就写好。后端所有接口返回结构统一为{code, message, data}结构前端axios封装响应拦截器之后只需处理业务状态码。这个设计从第一个接口就要遵守不然后期改造成本非常高。3.2 SQL脚本导入与关键数据初始化拿到这套源码之后第一步不是看代码而是先把SQL脚本导入本地数据库跑起来。数据库连接配置在application.yml文件里重点检查这几项spring: datasource: url: jdbc:mysql://localhost:3306/exam_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456导入SQL脚本时如果用的是Navicat直接右键数据库选择“运行SQL文件”即可。如果SQL文件较大或者表数量较多建议用命令行方式导入避免图形化工具可能出现的编码问题mysql -u root -p exam_system exam_system.sql脚本里的初始化数据通常包含一个默认管理员账号和密码登录后第一件事就是改密码。数据库初始化数据的密码字段需要是BCrypt加密后的密文不要直接明文存储。很多初学者会把密码直接写成123456存进去登录时用BCrypt.matches(123456, 加密串)比较永远对不上。这里的经验是要么先用测试代码生成一个加密串再写进SQL脚本要么启动项目后用注册接口跑一遍拿到正确的密文。另外一个在生产环境必须做、但毕设里容易被忽略的点MySQL的sql_mode配置。如果本地MySQL版本是5.7以上的默认配置ONLY_FULL_GROUP_BY模式下一些分组查询的SQL会报错。考试系统里的统计功能经常涉及GROUP BY建议在数据库连接参数里加上allowMultiQueriestrue如果遇到SQL兼容问题最直接的解决办法是调整SQL写法而不是全局关闭sql_mode——答辩时如果被问到为什么这么处理答案就是“保证查询语义的正确性同时兼容不同版本的MySQL配置”。3.3 前端Vue工程搭建与依赖安装前端工程推荐直接用Vue CLI或者Vite初始化。Vue CLI的好处是生态成熟、模板多、上手快Vite的优势是启动速度快。如果你本地Node.js版本是16以上用Vite会更舒服npm run dev基本是秒开。但如果学校教材和周围同学都用Vue CLI那就跟环境走没必要为了新而新。需要安装的核心依赖# UI组件库 npm install element-ui --save # 路由 npm install vue-router3 --save # HTTP请求库 npm install axios --save # 状态管理简单场景可以直接不装 npm install vuex --save这里有一个小细节如果用的是Vue 2.6Element UI直接按官网文档安装就好如果用的Vue 3则需要换成Element Plus两者API差异很大别用混了。Vue 2和Vue 3的路由版本也不一样——Vue 2对应vue-router 3.xVue 3对应vue-router 4.x很多同学在这里踩过坑页面白屏半天发现是版本不匹配。前端工程的目录结构建议按功能域组织src/ api/ // 接口请求封装模块 router/ // 路由配置 views/ // 页面级组件 admin/ teacher/ student/ components/ // 公共组件 utils/ // 工具函数如request封装 store/ // Vuex状态管理axios封装的核心代码就这么一段生产环境基本也是这个模式import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message)) } return res.data }, error { return Promise.reject(error) } )前端访问接口时的跨域问题最常见的解决方案是在vue.config.js里配置devServer代理module.exports { devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }这样前端开发时请求/api/login会代理到http://localhost:8081/api/login绕开了浏览器跨域限制。部署到线上后用Nginx反代同样配置一遍就可以了。3.4 后端接口开发与前端联调的协作方式接口联调阶段是毕设整个开发周期里最容易拖拉期的阶段。很多同学写后端接口时没有把入参出参定义清楚前端拿到数据之后还要再做一层适配两边来回扯皮。用接口文档把约定固定下来会减少大量无效沟通。下面是一份考试系统里最小可用的接口约定模板我建议在需求分析阶段就手写一版不用等后端写完再补// 获取考试列表接口 GET /api/student/exam/list?pageNum1pageSize10 // 请求头 Authorization: eyJhbGciOiJIUzI1NiJ9.xxx // 响应示例 { code: 200, message: success, data: { total: 20, list: [ { id: 1, title: 期中考试-Java基础, duration: 90, totalScore: 100, status: 1, startTime: 2024-05-01 09:00:00, endTime: 2024-05-01 11:00:00 } ] } }状态码定义要有全局统一的语义。200表示成功400表示参数错误401表示未认证或Token过期403表示无权限500表示服务端异常。前端axios拦截器里根据code统一处理跳转和错误提示打全局的消息提示业务代码里就不再到处写console.log和alert了。前端的接口请求模块按后端Controller的领域划分分文件维护。比如api/user.js管用户相关接口api/exam.js管考试相关接口每个文件里导出按接口名命名的函数。联调的时候打开浏览器F12的Network面板请求方法GET/POST、请求路径、请求头、响应体一目了然哪一侧出了问题一查就知道。3.5 打包部署与演示环境准备最后一步是打包部署。后端打包前先确认application.yml里的数据库连接指向的是演示环境可用的数据库然后执行mvn clean package -DskipTests打出来的Jar包在target目录下体积一般在40到80MB之间。启动命令用java -jar exam-system.jar --server.port8081前端打包npm run build打包完成后生成dist目录里面的index.html和static目录上传到服务器用Nginx配置一个静态站点和反向代理。Nginx核心配置长这样server { listen 80; server_name localhost; location / { root /usr/share/nginx/html/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这行是必写的因为Vue Router在history模式下刷新非首页的路由时会请求服务器上的真实路径如果没有这个配置就会404。如果不想配置Nginx也可以用history改成hash模式URL里会带个#号但演示效果比history模式差一点。演示环境准备的重点建议提前把所有演示数据准备好找一个已经发布状态、有学生已交卷、有成绩统计结果的完整数据快照。答辩时评委随口问“这个及格率是怎么统计的”你直接点开页面就能看到一个有数据的图表画面而不是临时造数据。另外准备一个备用浏览器和手机热点防止现场网络故障导致前端资源加载不出来。4. 接口文档的作用与编写规范4.1 接口文档在毕设里的定位很多毕设项目里的“接口文档”就是给答辩老师看的一个Word附件或者项目README里贴几个curl命令。但接口文档的真正价值是在前后端分离的开发模式下充当双方协作的契约。你可以把它理解为施工图纸后端按图纸施工前端按图纸接线任何人不需要翻源码就知道每个接口长什么样、怎么调用。在考试系统项目里接口文档的核心内容有四块接口名称与路径、请求方式、请求参数、响应结果。我用这套源码里的两个典型接口来说明规范的重要性。登录接口POST /api/auth/login Content-Type: application/json 请求体 { username: admin, password: 123456 } 响应体 { code: 200, message: 登录成功, data: { token: eyJhbGciOiJIUzI1NiJ9.xxx, userInfo: { id: 1, username: admin, realName: 系统管理员, role: 1 } } }交卷接口POST /api/student/exam/submit Content-Type: application/json Authorization: eyJhbGciOiJIUzI1NiJ9.xxx 请求体 { recordId: 12, answers: [ { questionId: 3, studentAnswer: A }, { questionId: 7, studentAnswer: T }, { questionId: 15, studentAnswer: Java是跨平台语言核心在于JVM屏蔽了操作系统差异 } ] }响应的时候后端不应该只返回一个“交卷成功”的提示而应该把得分情况一并返回。这样前端可以直接跳转成绩页展示结果省去再次查询接口的请求。这里的经验准则是一个业务流程中前端必然需要的数据尽量在一次请求的响应里给全。4.2 在线文档工具与维护节奏接口文档如果用手写Markdown维护版本一多就容易跟代码脱节。如果毕设要求不高手写文档也够用但更推荐的方案是用Apifox或者Swagger来维护。Apifox的优势是中文界面、上手零成本支持从代码注解自动生成接口文档也支持在线调试前端同学可以直接在文档里调通接口再写页面。Swagger是Java后端的老牌方案通过注解把接口信息暴露到/swagger-ui.html页面。我用Apifox的实际体验是把接口调试、Mock数据、文档管理合并到了同一个工具里后端写完接口之后可以直接一键调试前端拿到分享链接就能看到接口定义甚至可以直接调用接口来联调不需要在IDE和浏览器之间来回切换。接口文档的维护节奏应该是开发一个接口同步更新一份文档。不要等所有接口写完再回头补那样大概率会漏掉或者写得不准确。每个接口标明“最后更新时间”联调阶段如果改了字段改完代码顺手更新文档保持文档和实际接口的一致性。4.3 让接口文档成为答辩加分项答辩时如果被问到“前后端如何协作”如果你能从包里拿出手机打开Apifox文档展示登录接口的请求参数和响应字段再切换到接口调试功能现场调用一个接口哪怕不写任何代码评委也能直接看出你有真实的工程协作意识。写接口文档还有一个小技巧在接口描述里补充业务说明。比如交卷接口的文档里写清楚“后端会自动判分客观题主观题状态变为待阅卷响应中的score字段表示客观题得分”之类的话。这个信息写不写进接口文档体现的是两种完全不同的思维方式——前者是“我写的接口我自己清楚就行”后者是“跟我协作的人不需要读我代码也能正确对接”。5. 常见问题排查与踩坑记录5.1 启动类问题速查我整理了一份从启动到联调最常见的异常情况对照表这四条基本占了毕设阶段八成的问题1. 启动报Failed to configure a DataSource这是典型的数据库连接没配好。检查application.yml里的URL、用户名、密码是否正确MySQL服务有没有启动有没有执行过SQL脚本初始化数据库。注意MySQL 8.x和5.x的连接驱动不同8.x的driverClassName是com.mysql.cj.jdbc.Driver5.x是com.mysql.jdbc.Driver版本不对也会报错。2. 启动时报端口被占用后端默认端口8080被占用的情况很常见。最简单的方式就是换一个端口java -jar xxx.jar --server.port8081。如果想排查占用端口的进程Windows下用netstat -ano | findstr 8080查到PID再去任务管理器里结束进程Mac下用lsof -i :8080。3. 访问接口报401先看请求头有没有带上Authorization再看Token是否过期。前端登录成功后要把Token存到localStorage里axios请求拦截器里统一加请求头。排查时先打开F12看Network面板确认请求头里确实带着Token再确认后端JWT解析逻辑没有把Bearer前缀重复拼接。4. SQL脚本导入后中文乱码数据库连接URL里加上characterEncodingutf8SQL文件本身另存为UTF-8编码。用Navicat导入时注意选择编码格式命令行导入前先执行SET NAMES utf8mb4;。5.2 前后端联调与考试流程问题学生交卷后成绩不显示这是考试系统里最需要关注的问题之一按照我的排查经验优先级顺序是先查exam_record表里这条记录的status字段是不是“已交卷”状态再查自动判分逻辑有没有真实执行、有没有被事务回滚最后查前端“成绩查看”接口拿到的recordId跟答题明细关联是否正确。一个常见低级错误是前端交卷成功后跳转到成绩页时带着的是试卷ID而不是考试记录ID后端自然查不到这条记录对应的成绩。考试中途刷新页面后答题状态丢失这个跟答案暂存策略有关。答题暂存有两种实现方案一是每答一题立即调接口保存答案二是前端内存里暂存只在特定时机批量保存。如果选择方案二但时机设计不好刷新页面肯定丢答案。建议用方案一每切换一题就把刚答的那一题答案通过/api/student/exam/saveAnswer接口提交。接口可以做幂等设计同一题多次提交以最后一次为准逻辑简单又稳。倒计时与后端时间不同步出现这个情况基本就是只在前端做了倒计时。解决方案前面提过进入考试时后端把当前服务器时间传给前端前端计算start_time duration - now作为剩余秒数每隔一分钟后端重新同步一次时间。这样即使前端网络波动、系统休眠也不会因为本地时间误差导致提前交卷或超时未交。5.3 部署上线阶段的问题前端页面刷新404部署后刷新非首页路由出现404基本就是Nginx没有配置try_files或者后端服务没启动。把try_files $uri $uri/ /index.html;加上去就好了。跨域拦截线上部署后浏览器控制台报CORS错误先确认请求是走Nginx代理的还是直接指向后端端口的。如果前端部署在Nginx上后端也在同一台机器上建议前端请求写成相对路径/api/xxx并通过Nginx反向代理到后端不需要跨域。如果前端和后端域名不同才需要后端配置CORS。5.4 给答辩加分的几个细节首页Dashboard统计设计很多考试系统的首页只有一个欢迎语这是浪费了展示机会。加上简单的数据看板——学生总数、试卷总数、今日考试人数、及格率用ECharts画三个图表考试趋势、成绩分布、题目正确率后端只需要写一个聚合查询接口。这几个图表放在首页评委第一眼看过去对项目的完整度评价完全不一样。权限控制的补充说明在论文或者README里写清楚本项目基于角色对接口进行访问控制不同角色登录后看到不同的功能菜单。这种细节不用写复杂但一定要写到。日志打印规范Controller层打印业务日志Service层打印入参出参出现异常统一记录错误日志。答辩现场如果调试出问题打开控制台能快速定位而且规范的日志习惯本身就是工程能力的一种体现。密码加密存储用户表密码必须用BCrypt加密登录用BCrypt.matches()校验。论文里写“密码以密文形式存储于数据库保障用户数据安全”这个是安全意识的加分项成本几乎为零但很多人没做。6. 关于这套源码的扩展与思考考试系统这个题目做成一套能用、能演示、能答辩的源码并不难但如果你想在“能跑”的基础上再多想一步有几个方向值得琢磨。一个是防作弊设计。在线考试和线下考试最大的区别在于没有办法保证考生独立作答。实际工程项目里有摄像头监控、屏幕录制、禁止切屏、随机打乱题目顺序和选项顺序等手段。毕设系统如果能把“切屏检测”做出来——监听visibilitychange事件记录离开页面的次数和时间——这个点就比大多数同题目的毕设有亮点。另一个是智能组卷的进阶。目前多数系统的智能组卷是纯随机抽取如果引入简单的难度配比策略比如困难题占比20%、中等30%、简单50%难度系数存在题目表里按比例抽取并且保证同一张试卷里不出现完全重复的题目类型和难度组合。这个逻辑用几行SQL就能实现但在论文里能写一整节算法设计。还有统计分析的可视化扩展。当前系统的统计图表是二维的——得分分布、及格率这些。如果加上“班级维度”、“时间维度”的分析比如按月份统计考试次数和平均分的变化趋势后端就是一个GROUP BY的SQL前端画个折线图但评委看起来会觉得系统有数据分析的味道。我个人在实际操作中的体会是考试系统到后期最大的瓶颈往往不是技术而是对业务细节的把控。学生交卷之后能不能重新进入试卷教师阅卷的时候能不能看到题目标准答案试卷发布之后能不能撤回这些业务规则在需求分析阶段就要想清楚不然写代码时反复改来改去会非常消耗耐心。如果你打算拿这套源码当基础改写成自己的毕设我建议你从两个点入手一是换皮肤把Element的默认主题色改掉或者调整一下登录页的布局视觉上最先被注意到二是加一个“自建选修课考试”或者“班级考试排名”这种有差异化的功能模块哪怕逻辑简单也是你在这个项目里留下的独立工作痕迹。毕业设计这个东西做得好不好很大程度上在于你能不能把“为什么这么设计”讲清楚。技术可以借鉴但思路和表达必须是你自己的。