ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue前后端分离在线考试系统完整项目实战解析

SpringBoot+Vue前后端分离在线考试系统完整项目实战解析 前后端分离这个词这几年前后端开发基本绕不开。但说句实话市面上能讲清楚“前后端分离”概念的资料很多能给你一套完整可运行的源码、还带详细部署教程的真不多。这套基于SpringBootVueMyBatisMySQL的在线考试与学习交流网页平台系统算是我这几年带项目、做毕设辅导、给企业做内部培训沉淀下来的一套完整方案。它不只是一个简单的前后端分离Demo而是一个包含在线考试、个人学习记录、社区交流互动、后台管理等多个真实业务模块的完整系统前后端代码全部开源环境配置、数据库脚本、部署流程都整理成了文档照着走就能跑起来。这套系统最直接的用途有三个一是给正在学SpringBoot和Vue的开发者当实战练手项目二是给计算机相关专业的学生做毕业设计或课程设计三是给想快速搭建内部考试或培训系统的团队做基础原型。它的技术栈非常主流——SpringBoot做后端接口服务Vue做前端页面MyBatis负责数据库操作MySQL存储数据前后端通过JSON格式交互完全符合当下企业级项目的开发模式。通过这套系统你能看到用户登录注册、题库管理、在线答题、自动判分、成绩统计、帖子发布、评论互动、后台数据看板这些完整业务链路的真实实现方式也能学会前后端分离项目从开发到部署的全流程。1. 项目整体设计为什么选这个技术组合和功能架构1.1 技术选型的核心逻辑很多新手在刚接触前后端分离时最大的困惑不是“怎么写代码”而是“为什么用这个技术”。我先说清楚这套系统的技术选型逻辑后面你理解代码时就会顺畅很多。后端选择SpringBoot核心原因是它在Java生态里几乎成了事实标准。SpringBoot通过自动配置大幅减少了传统Spring项目的XML配置量内嵌Tomcat服务器一个jar包就能启动整个后端服务。这里要特别提醒一下你在SpringBoot官网下载项目模板时如果勾选Web依赖默认内嵌的是Tomcat如果选了Reactive Web那默认就是Netty。这套系统用的是传统Servlet Web模式所以内嵌Tomcat部署时既可以直接用java -jar跑也可以单独装一个Tomcat用war包部署两种方式教程里都写了。前端选择Vue是因为Vue的渐进式框架特性对中小型项目非常友好。你可以只用它的核心库做页面渲染和数据绑定也可以配合Vue Router做前端路由、Vuex或Pinia做状态管理。这套系统用的是Vue 2 Vue Router Element UI的组合为什么不用Vue 3因为这套系统最初开发时候Vue 3生态还没完全成熟很多组件库和插件兼容性不如Vue 2稳定。如果你现在自己从零开发新项目直接用Vue 3 Vite Element Plus会更好但学习这套基于Vue 2的源码依然有参考价值——它的路由配置、Axios封装、组件通信思路在Vue 3里同样适用。持久层选择MyBatis很接地气的一个原因如果你做过企业项目会遇到大量复杂SQL查询、多表联查、动态条件拼接的需求Spring Data JPA虽然自动化程度高但在SQL灵活控制上不如MyBatis直接。MyBatis让你把SQL写在Mapper XML文件里每个字段怎么查、怎么关联、怎么判空一目了然。这套系统里像“考试记录列表需要关联用户表、试卷表、成绩表”这种多表查询用MyBatis的ResultMap做结果映射比JPA的派生查询直观得多。数据库选MySQL因为它是开源免费里最主流的关系型数据库生态成熟、资料丰富。这套系统用的是MySQL 5.7版本因为5.7的InnoDB引擎、JSON字段支持、性能表现都很稳定。当然MySQL 8.0也完全兼容只是需要注意8.0的驱动包和连接配置稍有区别教程里都做了说明。1.2 系统功能模块与角色权限设计这套系统我从一开始就按“真实可用”的标准来设计不是那种只有一个登录页和一张表的作业。全系统按角色分成三大端管理员端、教师端、学生端外加一个公共的登录注册模块。先看核心业务——在线考试模块。考试不是一个简单的“出题-答题-出分”流程它背后牵扯到一套完整的业务闭环。题库管理负责按类型维护选择题、判断题、填空题试卷管理负责从题库中选题组卷设置试卷总分、考试时长、及格分数在线考试负责学生进入考试、计时答题、保存答卷自动判分负责客观题按标准答案比对判分主观题支持手动批改。这里有个很关键的细节考试中途的作答数据要实时保存到数据库而不是等交卷时一次性提交否则学生中途浏览器崩溃或者误关页面前面答的题全丢了。这个设计我在后面的数据库章节会详细讲。学习交流模块就是论坛帖子功能。用户可以发布帖子、浏览帖子列表、发表评论回复。这个模块看起来简单但它涉及用户信息关联、帖子列表分页查询、评论的嵌套展示是练习MyBatis多表联查和Vue组件通信的好素材。管理员端的功能集中在上半部分用户管理包括学生和教师账号的增删改查、状态禁用、课程管理维护课程信息、新闻公告管理发布平台公告、数据统计看板展示用户注册数量趋势、考试次数统计、及格率分布。其中的数据统计需要SQL对时间字段做分组聚合再通过ECharts在前端做图表渲染这一块对理解后端返回结构化数据、前端做可视化展示很有帮助。系统从前端页面数量来看有登录注册页、学生端考试中心、学生端学习社区、个人中心、教师端试卷管理、管理员端用户管理等十几个页面。路由数量多、组件交互复杂反而成了练手的好机会——你能在真实项目里体会到为什么需要Vue Router来管理路由跳转为什么需要Axios拦截器统一处理HTTP请求。2. 数据库设计一套在线考试系统的表结构是怎么规划的2.1 核心表结构与字段设计思路拿到一套源码后很多人第一个动作是打开application.yml看数据库账号密码第二个动作是运行SQL脚本建库建表。这没错但我建议你先静下心看一遍数据库表结构设计这是理解整个系统业务逻辑最快的方式。这套系统的数据库一共有十几张表核心的业务表可以分成四组。用户体系三件套sys_user用户表、sys_role角色表、sys_user_role用户角色关联表。为什么用关联表而不是在用户表里直接加一个role字段因为用户和角色是多对多的关系一个用户可能有多个角色直接加字段会导致数据冗余和扩展困难。这个设计在企业项目中是标准做法你以后做任何带权限管理的系统都可以直接复用这套RBAC基于角色的访问控制模型。题库与试卷体系是考试系统的命脉包含question_bank题库表、exam_paper试卷表、paper_question试卷题目关联表。这里有个核心设计点为什么需要paper_question这张关联表因为一份试卷要包含多个题目而一个题目可能出现在多份试卷里如果不建关联表就得在试卷表里存题目ID字符串或者把题目冗余到试卷表里两种做法都不优雅。关联表本身还承载了额外信息——比如题目在当前试卷中的分值占比、题目顺序这些字段为什么不能存在题库表里因为同一个题目在不同试卷里的分值和顺序可能不同这是业务逻辑决定的。在线考试体系包含exam_record考试记录表、answer_record作答记录明细表。exam_record保存一次考试的整体信息——哪个用户、哪份试卷、开始时间、交卷时间、最终得分、状态未开始/进行中/已交卷/已判分。answer_record保存每一道题的具体作答内容——用户选的选项、填写的答案、该题的得分。这两张表的搭配相当于订单表和订单明细表的关系。社区互动体系是topic帖子表和topic_reply回帖评论表。帖子表存发帖人ID、标题、正文内容、发布时间、浏览数回帖表存所属帖子ID、评论人ID、评论内容、回复时间。这里涉及一个细节如果同时实现“回复某条评论”的功能回帖表里还需要一个parent_id字段表示这条回复是回复主题的还是回复某条评论的。2.2 考试中途崩溃不丢数据的关键设计我在前面留了个悬念——考试中途作答数据实时保存。这是我在实际开发中被用户“骂”出来的经验。最初版本的代码是前端把所有答案放在内存里点交卷时一次性发给后端。结果是学生用着用着浏览器不支持某个插件就白屏了或者误关了标签页重新登录后考试记录还显示“未开始”前面答的题全没了。学生在群里吐槽说“这系统有bug我答了半小时的题白答了”——实际上系统逻辑是对的但用户体验极其糟糕。后来调整方案核心思路是把“整场考试”拆分成“每个动作”学生每选择一题或填写一空答案前端就调一次接口把这个题目对应的answer_record更新到数据库。同时exam_record表里维护一个status字段和last_answer_time字段。这样即使中途退出重新进入时按exam_record查到最后答题时间前端能恢复到退出前的题目状态。这里涉及的数据库设计问题实时保存带来的写压力大不大实际算下来一个学生一场考试按30道题算每道题保存一次就是30次写操作。100个学生同时考试就是3000次写操作对MySQL来说完全扛得住不需要引入消息队列做削峰填谷。这也是为什么这套系统选择MySQL而不是更重的方案——业务场景决定了技术复杂度。这个设计思路对你有借鉴意义先把最简单的方案跑通遇到问题再逐步优化不要一开始就上超前的架构。2.3 MySQL配置与索引优化的细节处理建表SQL里我做了几个细节处理这里一并说一下。第一所有表的主键都用自增ID的吗不全是的。用户表的主键是自增ID没问题但试卷表用了begin_time和end_time两个时间字段记录考试的起止时间而不是单靠一个创建时间字段。这样查询“正在进行的考试”只需要一条SQLwhere begin_time now() and end_time now()索引优化空间更大。第二索引的创建原则。我的建议是查询频繁的WHERE条件字段加索引关联查询的字段必须加索引但不要对所有字段无脑加索引。这套系统里sys_user表的username字段加了唯一索引因为登录时按用户名精确查找同时唯一索引能确保用户名不重复。topic表的user_id字段加了普通索引因为要频繁查询某个人发过的所有帖子。而answer_record这种写多读少的表没有过多加索引不然会导致数据写入变慢。第三字符集和排序规则选择utf8mb4而不是utf8。因为utf8在MySQL里最多存3个字节的字符遇到Emoji表情或少见生僻字时就报错了。utf8mb4是utf8的超集能完整支持4字节UTF-8字符。登录注册模块的用户昵称万一谁加了表情符号你再也不用担心报错。第四MySQL 8.0和5.7有一点不同需要特别注意8.0默认的排序规则是utf8mb4_0900_ai_ci5.7一般是utf8mb4_general_ci。如果你本机是8.0导入5.7的SQL脚本时可能提示排序规则冲突解决方式是在导出的SQL文件里全局替换排序规则名称或者建库时就统一指定。这个问题卡过不少刚装MySQL 8.0的新手我特意在部署文档里写了解决方案。注意MySQL安装时务必记住你配置的root账号密码并且确认端口号是默认的3306。如果之前装过其他版本的MySQL要先去服务管理器里停掉旧的MySQL服务否则新旧两个实例抢3306端口会导致你SpringBoot项目一直报连接失败的错误。3. 核心功能实现从认证鉴权到在线答题的完整链路3.1 前后端交互与跨域问题的处理前后端分离项目里最容易被新手卡住的就是“接口调不通”。明明后端启动了前端页面也打开了但页面上一点按钮控制台就报错。十有八九是跨域问题。跨域的本质是浏览器的同源策略限制协议、域名、端口三个有一个不同浏览器就认为这是一次跨域请求会拦截响应。前后端分离开发时前端在8080端口后端在8081或8082端口端口不同天然跨域。这套系统的解决方式是使用CORS跨域资源共享全局配置。在SpringBoot中配置一个WebMvcConfigurer实现类重写addCorsMappings方法允许前端服务器的地址跨域访问。关键代码如下Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }这里有个细节allowedOrigins不能设置成*星号同时allowCredentials又为true。因为浏览器规定了两者不能同时生效。要么把前端域名写死要么用allowedOriginPatterns配合allowCredentials使用。日志里如果报“When allowCredentials is true, allowedOrigins cannot contain the value *”这个错你就要检查这里。另一个解决跨域的手段是Nginx反向代理让前端和后端对外都是同一个域名只是路径前缀不同。比如/api开头的请求由Nginx转发到后端8082端口其他请求走前端静态文件。这种方式在生产环境更常用部署章节我会详细说。3.2 登录认证与权限控制从Session到JWT的演进这套系统的登录认证经历了从Session到JWT的演进过程我把两种方式的优劣都写清楚方便你以后在面试或实际项目中做技术选型。Session方案是传统单体应用的标配用户登录成功后后端把用户信息存到服务端内存或Redis同时生成一个带SessionID的Cookie下发到浏览器。后续每次请求浏览器自动携带Cookie后端通过SessionID识别用户身份。优点是被广泛使用、方案成熟缺点是前后端分离架构下Cookie在跨域请求中的携带比较麻烦而且如果后端是多实例部署Session还得做共享。JWTJSON Web Token方案是目前前后端分离项目的主流选择。用户登录成功后后端生成一个经过签名加密的Token字符串返回给前端。前端把Token存在localStorage或sessionStorage里每次请求时在HTTP请求头里带上Authorization: Bearer 。后端写一个拦截器或过滤器每次请求先解析Token、验证签名的有效性、读取用户信息然后放行请求继续执行。这套系统用的是基于JWT的拦截器实现方案。核心流程是登录接口接收到用户名密码校验通过后用系统配置的密钥生成Token返回前端在Axios请求拦截器里统一从localStorage取出Token并添加到请求头后端定义了一个LoginInterceptor拦截器在preHandle方法里从请求头取出Token并调用JWT工具类解析。解析失败就返回401状态码解析成功就把用户ID和用户名放入Request域中方便后续业务代码获取当前登录用户。Token过期时间的管理这里说几个细节。这套系统的Token有效期设置的是24小时原因很直接——考试系统的使用场景是学生考前集中登录刷题如果没到8个小时就被强制下线重新登录用户会很恼火。但很多企业后台管理系统会设置成2小时甚至30分钟配合刷新Token机制来平衡安全性和用户体验。Token在服务端如何主动让某个用户的Token失效JWT本身是无状态的服务端不存储已签发的Token所以做不到主动失效。如果需要“强制下线”某个用户就得引入Token黑名单机制——在Redis里记录被拉黑Token的ID每次请求解析时比对一下是否在黑名单里。这套系统因为业务不涉及强权限管控暂未做这个功能但实现思路已经写进技术文档里了。3.3 在线答题流程与自动判分的实现在线考试模块核心流程分三步进入考试、答题保存、交卷判分。进入考试时学生点击“开始考试”按钮前端判断当前试卷处于考试时间内然后向后端发起请求——创建一条exam_record记录状态为“进行中”。后端返回的响应里包含这份试卷的所有题目信息前端把这些题目渲染成表单。这里有一个要注意的设计从考试开始那一刻返回到前端的题目数据里不应该包含正确答案字段。不然学生用浏览器开发者工具查看接口响应不就等于提前看到答案了吗正确答案只能在交卷判分阶段由后端在内部查询使用不返回给前端。答题保存我在数据库章节已经说了前端监听到用户的选项变更或填空题的输入框失焦立即调用保存接口把这道题的作答情况写入answer_record表。这里的“实时性”价值在遇到浏览器崩溃的极端场景时最能体现。交卷判分分为两种场景。如果是一直答到时间耗尽自动交卷前端在倒计时归零时自动触发交卷接口如果是学生手动点击交卷同样触发交卷接口。后端接收到交卷请求先校验当前考试记录状态是否已经是“已交卷”防止重复提交。然后查询该试卷的所有题目列表逐题比对answer_record中学生的答案和question_bank中的标准答案客观题选择和判断直接字符串匹配判分。每道题的分值相加得到总分更新exam_record的score字段。主观题填空题部分需要人工判断则先标记为“待人工批改”由教师端登录后在批改页面逐题给分。这里自动判分代码里有一个隐蔽的坑判断题的答案字符串在数据库存的是“正确”和“错误”选择题存的是“A”“B”“C”“D”但用户提交的答案到了后端后如果前后端对字符串的编码不一致比如前端多了一个空格就会导致判断错误本来是正确答案却被判成零分。所以我在后端处理时统一做了一次trim()去空格再和标准答案比较这个隐患就解决了。你以后自己写判分逻辑时输入数据的清洗一定是第一个要做的事。3.4 学习交流模块与MyBatis联表查询实战学习交流模块是很多学生觉得“最容易”的模块因为它不就发帖子、回帖子嘛。但真正写起来才知道帖子列表页面一般需要显示的信息包括帖子标题、摘要、发帖人昵称、回复数量、最后回复时间。这些数据分散在不同表里——帖子基本信息在topic表发帖人昵称在sys_user表回复数量需要COUNT聚合topic_reply表。这就是联表查询。MyBatis在这块的优势体现得很明显。我在TopicMapper.xml里写了一条带条件动态查询的SQL核心是多表LEFT JOIN和动态条件构造。看下面的片段select idselectTopicPage resultMapTopicWithUserMap SELECT t.id, t.title, t.content, t.create_time, t.view_count, u.nick_name, (SELECT COUNT(*) FROM topic_reply r WHERE r.topic_id t.id) AS reply_count FROM topic t LEFT JOIN sys_user u ON t.user_id u.id where if testkeyword ! null and keyword ! AND (t.title LIKE CONCAT(%, #{keyword}, %) OR t.content LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY t.create_time DESC /select这条SQL里用了子查询来计算回复数实际数据量大时可以把子查询改成LEFT JOIN后按topic_id分组性能差异在数据量大了以后才能感觉到。这套系统的数据量在练习场景下不大负责实现业务逻辑比追求极端性能更重要。MyBatis分页建议使用PageHelper插件这个插件在搜索结果里也是高频出现的。PageHelper用起来非常简单在Mapper查询前调用PageHelper.startPage(pageNum, pageSize)然后正常执行查询MyBatis会自动帮你在SQL后拼上LIMIT语句并且查询结果会封装进PageInfo对象里拿到total、pages这些分页元数据。使用时有几个注意事项第一PageHelper只对紧跟其后的第一条查询生效如果你在startPage和Mapper查询之间还执行了其他SQL语句分页就失效了第二不要对查询结果再做二次查询操作否则可能页码错乱第三多表联查时PageHelper默认用COUNT(*)优化自动生成计数SQL一般能用原生SQL处理。4. 环境准备与项目部署从零到能跑的完整实操4.1 开发环境安装配置清单动手跑这套系统前先把环境准备好。整套环境清单如下JDK 1.8及以上推荐8SpringBoot 2.x兼容性最好Maven 3.6MySQL 5.7或8.0本地安装后创建数据库执行项目里的init.sql脚本完成建库建表Node.js 12以上建议14或16Vue 2项目对这版本比较友好Nginx生产部署前端用或Tomcat后端war包部署用开发IDE后端IntelliJ IDEA前端VS Code环境安装过程中的细节往往比代码本身更影响进度。就MySQL安装来讲不同系统踩坑点不一样。Windows环境最容易出问题的就是MySQL服务没起来优先检查服务管理器里MySQL服务是否已启动然后检查3306端口是否被占用命令行执行netstat -ano | findstr 3306看到进程编号后去任务管理器确认是否为mysqld.exe。Linux环境下安装MySQL 5.7则需要先检查是否已经装了MariaDB两者默认抢同一个端口存在冲突会导致MySQL起不来。Vue环境搭建也有一个高频报错执行npm install时网络问题导致依赖安装失败。常见解决方案是切换到国内镜像源配置registry。具体命令为npm config set registry https://registry.npmmirror.com。配置之后重新执行npm install即可。如果node_modules损坏了最简单的做法是直接删除node_modules文件夹后重新安装别纠结去修补。4.2 后端打包与前端构建全流程后端打包和前端构建是整个部署过程中最容易出错也是最多人问的部分。先说后端。后端项目使用Maven构建在IDEA右侧Maven面板选择Lifecycle下的clean和package执行打包操作。打包完成后项目target目录下会出现一个jar包文件名一般是项目名加版本号比如exam-server-0.0.1-SNAPSHOT.jar。这个是SpringBoot默认的可执行jar包里面包含了内嵌的Tomcat和所有依赖直接命令行运行时细心的人可能会问SpringBoot默认打成jar包后用java -jar就能跑为什么还需要单独安装Tomcat去部署war包呢原因是有些公司的运维规范要求统一用Tomcat管理多个Java应用或者服务器上已经有Tomcat统一用Tomcat部署更符合现有流程。所以这套系统的部署文档里写了两套方案两套方案的应用场景不一样你可以按需选择。打war包需要pom.xml里设置packaging为war同时SpringBoot启动类继承SpringBootServletInitializer并重写configure方法这两步缺一不可。否则war包放到Tomcat的webapps目录下Tomcat能识别但应用初始化会失败。前端构建相对简单。在项目根目录执行npm install安装依赖然后执行npm run build构建生产包。构建完成后项目dist目录下会出现一堆静态文件——index.html、css文件夹、js文件夹。这些就是前端最终的产品形态。实际上Vue项目编译完之后已经是纯静态资源了你用任何Web服务器都能托管它不一定是NginxApache、Tomcat、甚至Python的自带HTTP服务都可以。但生产环境一般用Nginx性能和静态资源缓存策略更好。构建过程中容易出现的一个问题npm install执行到一半卡住不动。这种情况往往是网络波动或依赖源响应慢优先把registry切换为国内镜像源再重试。另一个问题最后npm run build报错Cannot find module xxx。先别急着搜报错信息大概率是某个依赖在npm install时没有完整安装或者是依赖版本冲突。最快速的方案是删除package-lock.json文件和node_modules目录重新npm install。如果还不行检查node_modules里有没有出现。遇到报错先重装依赖解决不了再查具体依赖版本。4.3 Nginx配置和文件结构关于Nginx配置我给出这套系统可用的参考配置server { listen 80; server_name localhost; root /usr/share/nginx/html/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }配置里的关键点我逐一拆解。location /的try_files指令是Vue Router的history模式必须配置的否则用户刷新页面时Nginx会直接去磁盘上找对应的路由路径比如用户访问了http://www.example.com/topicNginx去找/topic这个文件找不到就返回404了。try_files $uri $uri/ /index.html的意思是先尝试按当前URI查找文件找不到再尝试按目录查找还是找不到就统一返回index.html由前端Vue Router接管路由渲染出对应页面。location /api/的作用是反向代理。前端的Axios请求地址如果是/api/login经过Nginx后会被转发到真正的后端地址http://127.0.0.1:8082/api/login。这样浏览器感知不到跨域因为所有的请求走的都是同一个域名和端口的80转发是Nginx在服务端内部完成的。这也是生产环境中前后端分离项目解决跨域问题的标准做法。4.4 Windows和Linux两种服务器部署实操记录Windows服务器部署这套系统是比较省心的场景。如果你是在Windows上部署大致流程是先确认JDK、MySQL已经安装并启动成功。然后把你从IDEA打包好的jar包拷贝到服务器某个目录比如D:\app。然后在这个目录下新建一个启动脚本文件start.bat内容很简单主要是配置JDK路径并启动jar包。注意脚本文件保存时编码格式选ANSI否则中文注释或路径会乱码。启动后浏览器的访问地址就是http://服务器IP:8081。如果你想用80端口对外提供服务要么改SpringBoot配置文件里的server.port为80要么用Nginx做一层转发。Linux服务器部署其实和Windows没有本质区别只是少了一些图形界面的干扰执行命令更干净。把jar包上传到服务器后执行java -jar命令启动。这里我要强调一个新手常犯的错误直接在SSH终端里执行java -jar这样的话当你关闭SSH终端连接时这个应用进程就会被系统杀掉。正确做法是使用nohup命令将进程脱离终端后台运行通过退出码或日志确认启动成功。另外服务器上如果配置了防火墙需要开放SpringBoot应用对应的端口否则外部机器访问不到。什么时候需要装Tomcat如果你启动方式是war包部署时则需要安装匹配JDK版本的Tomcat把war包放到tomcat的webapps目录下启动Tomcat后它会自动解压war包并部署。部署完成后需要在Tomcat的conf/server.xml配置Host的appBase路径确保应用能正确解压和启动。5. 高频踩坑清单这些问题我当年都遇到过5.1 后端启动阶段的问题排查实际带学生、带团队成员过项目时后端启动失败的问题出现频率最高我把常见报错整理成一张速查表方便你直接对照排查。报错现象常见原因解决方案ApplicationContextException: Unable to start web server端口被占用或Tomcat启动失败检查8081端口是否被其他进程占用换端口或用netstat排查Communications link failure连接数据库失败MySQL服务未启动或账号密码错误先确认mysql服务已启动再看application.yml连接配置的账号密码是否正确Access denied for user rootlocalhostMySQL账号密码认证失败重置MySQL root密码或使用有权限的账号Unknown database exam_db数据库尚未创建执行init.sql前先CREATE DATABASE exam_db再执行建表脚本The server time zone value Öйú±ê׼ʱ¼äMySQL时区配置问题在URL连接参数中加serverTimezoneAsia/Shanghaijava.sql.SQLSyntaxErrorExceptionSQL语法错误核对SQL脚本是否完整执行特别是MySQL 8.0和5.7版本差异启动日志能看懂多少解题能力就有多强。这里教新手一个实用的排查思路SpringBoot报错时不要看第一行要看最后面几十行的Caused by部分真正的原因往往在链路的末端。特别是一长串异常堆栈里面只有最底部才是Root Cause。5.2 前端构建和运行阶段的问题排查前端阶段的问题通常集中在npm和浏览器调试两个环节这类问题五花八门但大多有固定套路可解决。报错现象常见原因解决方案npm install报ETIMEDOUT或卡住不动网络问题、镜像源连接超时配置npm国内镜像源后重试npm run build报Cannot find module依赖未完整安装或版本冲突删除node_modules和package-lock.json后重装Vue页面打开后样式错乱CSS文件未正确引入或构建异常检查public/index.html是否正确引用了构建后的css文件路径页面接口返回401Token缺失或过期F12打开浏览器开发者工具查看Network中请求头的Authorization字段前端控制台报跨域CORS错误后端CORS配置或Nginx代理未生效先确认request实际访问的URL是否正确再检查浏览器报错提示页面刷新后404Vue Router history模式未配置try_files在Nginx配置中给location /加try_files指令很多同学遇到前端报错习惯去搜索引擎找答案但前端问题排查最有效的路径是先在浏览器开发者工具里看Network面板。请求发了没有、状态码是多少、响应体是什么这几个信息能帮你判断到底是你代码的问题、后端接口的问题还是网络问题。比如一个常见的场景你打开页面之后发现列表数据是空的那先看Network里对应请求返回的是什么。如果返回的是空数组说明后端查询结果确实没数据如果请求都没发出来那就是前端逻辑有bug。找到问题在哪一层比盲目搜报错信息效率高得多。5.3 MyBatis与MySQL的版本兼容性问题这套系统还有个容易卡人的地方就是MyBatis与MySQL驱动及数据库版本之间的兼容性问题。现在很多新项目直接用MySQL 8.0但如果你下载的SpringBoot版本是2.x早期版本默认依赖的MySQL驱动可能还是不兼容8.0的旧版本。典型报错是加载驱动类时提示找不到驱动或者连接后报CLIENT_PLUGIN_AUTH错误。解决方案很简单在pom.xml里显式引入mysql-connector-java的8.0版本依赖同时将JDBC URL里的driver-class-name改成com.mysql.cj.jdbc.Driver。同时注意时区参数因为MySQL 8.0的驱动在连接时对时区校验更严格URL里必须加上serverTimezoneAsia/Shanghai否则启动会直接报错。MyBatis本身的缓存问题也值得说道说道。MyBatis默认开启一级缓存也就是SqlSession级别的缓存同一SqlSession内相同SQL和参数的查询会命中缓存结果直接复用。但如果你的系统里在同一个事务中先插入了一条记录再查询这条记录一级缓存没被清空可能查到旧数据。解决方法是在MyBatis的Mapper XML中设置flushCachetrue或者查询时传入不同的参数。这套系统的代码里我特别关注了这个问题对于一些写操作后紧跟的查询我在Mapper XML中显式使用了useCachefalse配置避免缓存干扰业务逻辑。5.4 部署上线后的稳定性配置系统跑起来之后还有几个配置建议你一定得做不然上线后迟早出问题。第一个是数据库账号权限收紧。开发时为了方便经常直接用root账号生产环境强烈建议创建一个专用账号只授予当前数据库的所有权限不要给全局权限不要用root跑业务系统。这个习惯很多人有大教训数据库账号被脱库root权限直接沦陷整个服务器数据裸奔。创建一个专用账号的SQL也很简单几条语句就能搞定别懒。第二个是后端生产环境关闭调试信息。SpringBoot的application.yml里在开发环境日志级别设为DEBUG方便排查生产环境要改成INFO或WARN避免大量SQL日志拖慢系统也避免潜在的信息泄露。配置很直接改个日志级别的事但很多人因为开发环境没改就直接开生产日志文件几个小时塞满磁盘的情况我都见好几次了。第三个是前端部署时开启Gzip压缩。在Nginx的配置里添加gzip on再配合gzip_types和gzip_min_length相关参数静态资源体积能压缩50%以上页面加载速度肉眼可见的提升。前端构建的Vue项目js文件和css文件动辄几百KB甚至几MBGzip一压加载速度提升非常明显。写在最后这套系统我从最初只有一个考试模块的骨架一步步迭代到集题库、组卷、在线考试、自动判分、社区交流、后台管理于一体的完整平台前前后后花了大半年时间。期间踩过跨域配置的坑踩过MyBatis分页插件失效的坑踩过前端打包路径不对导致白屏的坑踩过Linux上部署后进程被系统杀掉的坑每一个坑都变成了这套系统文档里的一条注意事项。这也是为什么我一直强调看一百遍教程不如自己动手跑一遍项目、改几行代码、部署一次上线。如果你拿到这套源码我建议你的学习路径是先把开发环境跑起来然后跟着部署文档完整部署一遍最后再对照着源码和这篇博客去理解每一个模块的设计思路。不要停留在“能跑就行”的层面试着MySQL里改一改表结构在Vue组件里改一改请求参数在Mapper XML里改一改SQL关联条件然后看页面和数据库里的变化。这些动手经验才是这套源码真正值钱的地方。
返回列表