做在线考试系统这件事,我踩过的坑和攒下的经验,这次一次性说清楚。
先说结论:前后端分离 + Spring Boot + Vue3 + MyBatis + MySQL,这套组合用来做在线远程考试系统,是目前中小型项目里最稳、性价比最高的选型之一。我前后参与过两个类似的项目,其中一个就是完整走完这套技术栈从零开发到部署上线的,过程比想象中复杂,尤其是考试时间管控、自动评分、防作弊这些细节,稍不留神就是线上事故。
这篇博文我把整个项目的核心设计、实现细节、部署经验、常见坑点完整梳理出来,基于自己实操的总结,也会补一些合理推断的通用方案。无论你是刚接触这类系统想练手的开发者,还是公司内部要搭一个考试平台,都值得看完。
1. 项目整体设计与技术选型
1.1 为什么选前后端分离架构
你要做考试系统,第一件事不是写代码,是想清楚架构。我见过很多团队一上来就用传统模板渲染,页面和逻辑混在一起,后续光维护就够受的。考试系统的业务边界非常清楚:前台是考生答题,后台是教师出题、阅卷、统计。两边节奏完全不一样,考生端要高并发稳定,管理端要快速迭代功能,混在一起只会互相拖累。
前后端分离的好处,我实际感受下来有三点最明显:
第一,并行开发效率高。前端做页面交互的时候,后端可以同时把接口定义好、数据库建好,两边按接口文档开发即可,项目周期能压缩不少。
第二,部署灵活。考试高峰期可以把后端服务多挂几个实例,前端静态资源直接丢 CDN 或 Nginx,流量压力分散掉,不会出现一个人交卷卡死全场的尴尬。
第三,技术栈解耦。前端 Vue3 负责所有交互和渲染,后端 Spring Boot 只做业务逻辑和数据处理,职责清晰,后续前端想换技术栈、后端想拆微服务,成本都可控。
这套架构下,整体调用链大概是这样的:浏览器访问 Vue3 页面,页面通过 HTTP 请求调用后端接口,后端经过 Spring Boot 业务层处理,再通过 MyBatis 与 MySQL 交互,结果层层返回给前端展示。链路清晰,排查问题的时候顺着走就行。
1.2 技术栈选型的核心考量
选 Spring Boot,说白了就是看中它的生态成熟度和开发效率。Java 这门语言虽然被吐槽写起来繁琐,但 Spring Boot 把大量配置自动化之后,开发体验已经非常接近那些所谓“快语言”了。你只要引入对应场景的 starter,配置一个数据源,剩下的 CRUD 基本都是模板化操作。考试系统这种业务逻辑密集、事务要求高的项目,用 Spring Boot 是非常合适的选择。
Vue3 这边,核心看中的是组合式 API(Composition API)带来的逻辑复用能力。考试页面里有倒计时、题目切换、答案暂存、草稿、交卷确认这一大堆状态,如果用 Vue2 的选项式 API,scores 数据和定时器逻辑混在一起,代码会非常臃肿。Vue3 的setup语法可以按业务去组织逻辑,我习惯把答题计时、题目渲染、答案收集分别封装成独立的组合函数(useTimer、useQuestion、useAnswerSheet),每个函数管一件事,代码读起来清清楚楚。
MyBatis 在这个项目里承担的是数据库访问层。很多人纠结用不用 MyBatis-Plus,我的经验是:如果团队对原生 SQL 掌控力强,用纯 MyBatis 就好;如果想省事,引入 MyBatis-Plus 也没毛病,但要注意别把复杂的多表关联都甩给它生成。考试系统的数据模型不算复杂,但成绩统计那种多表聚合查询,我还是建议自己写 XML 里的 SQL,性能和可控性都更靠谱。
MySQL 则是仓储层的事实标准,事务支持、稳定性、社区资料都足够,对考试系统这种规模的项目没有理由不用。
2. 核心功能模块与数据库设计
2.1 考试业务的核心流程拆解
考试系统听上去功能不多,无非就是考试、阅卷、管理。但往细里拆,业务节点其实挺多的,我建议在动手前先画出完整的业务流程图,搞清楚每个环节的数据视角。
我自己习惯把考试系统划分为以下几个核心业务模块:
- 用户模块:登录注册、角色管理、个人信息维护。这里的角色至少要分三类:管理员、教师、考生。
- 题库模块:题目维护、分类管理、题目导入导出。按题型(单选、多选、判断、填空、简答)和难度分门别类。
- 组卷模块:创建考试、添加题目、设置考试时间、总分及格线、参加人员范围。这个模块决定了整场考试的参数和规则。
- 考试模块:考生在线答题、倒计时控制、答案暂存、自动交卷、意外断线恢复。这是整个系统压力最高、最容易出问题的地方。
- 阅卷模块:客观题自动判分,主观题人工评分,成绩核算与发布。
- 统计模块:成绩分布、及格率、答题明细、导出报表。
业务模块之间是有依赖关系的,比如组卷完成之后,考试模块才能读取试卷数据;阅卷模块又依赖考试模块的答题记录。设计好模块边界,后续每个模块独立开发、独立测试都方便。
2.2 数据库表设计与关系梳理
数据库表设计是整个系统的地基。地基没打牢,后续业务扩展起来非常痛苦。我实际项目里的核心表设计大致是这样的:
- 用户表(
sys_user):存放所有用户,包含角色字段、密码密文、状态等基础信息。 - 角色表与权限表:用 RBAC 模型,通过中间表把用户、角色、权限关联起来。Spring Security 做鉴权的时候直接从这套表里取数据。
- 题目表(
question):字段包括题干、选项(JSON存储)、题型、难度、答案、解析、所属课程。选项用 JSON 存,比拆成多张表灵活得多,前端解析也方便。 - 试卷表(
exam_paper):存考试的基本信息,比如考试名称、总时长、总分、及格线、开始结束时间、状态(草稿/已发布/进行中/已结束)。 - 试卷题目关联表(
exam_paper_question):记录某张试卷里有哪些题目,每道题多少分。这里不直接存题目内容,而是存题目ID,这样后续修改题目内容,所有引用它的试卷自动同步。 - 答题记录表(
exam_answer_record):核心表,管每名考生每道题的答案、得分、判分状态。这张表的数据量是最大的,要提前做好索引。 - 考试记录表(
exam_record):管考生整场考试的宏观信息,比如参考状态、总分、交卷时间、是否超时自动交卷。
设计的过程中有两个细节一定要重点考虑:
一是试卷快照问题。考试的试卷内容必须保证开考后不再变化,但教师可能随时改题库里题目的内容。解决方案是考试记录表里冗余存一份试卷相关字段,或者单独建一版“试卷快照表”,开考瞬间把所有题目内容复制一份存下来。这个设计对于正式校园考试来说至关重要,否则教师修改题干会导致批改数据错乱。
二是并发大流量下索引和分表的设计。答题记录表按考试ID + 考生ID + 题目ID建联合索引,查询非常快。如果学校考试人数过万,可以按考试ID做分表,Java 代码里做分表路由,用 MyBatis 拦截器统一处理即可。
2.3 关键接口设计与权限控制
接口设计上,我坚持两点:RESTful 风格统一,以及前后端接口字段规范一致。
登录、注册相关的接口做统一封装返回体,包含code、message、data三个字段。前端的 axios 拦截器统一处理code,比如遇到 401 就自动跳转登录页,遇到 403 就提示无权限。这种统一约定能省下很多前后端联调时扯皮的时间。
权限控制这块我实际用的是 Spring Security 配合 JWT。流程大致是:用户名密码登录成功后,后端生成 JWT 返回,前端把 Token 存起来,每次请求放到 Header 里。后端通过过滤器解析 Token,识别当前用户的身份,再按角色判断接口权限。
具体接口分这几类:
- 管理端接口:
/admin/**前缀,要求管理员或教师角色,负责用户管理、题库管理、考试管理。 - 考生端接口:
/exam/**前缀,要求考生角色,负责获取试卷、提交答案、交卷、查看成绩。 - 公共接口:登录、获取公开信息,不限制权限。
我建议把权限控制做成注解或者 AOP 切面的方式,避免每个 Controller 里都复制粘贴一大段鉴权逻辑。实际项目中我用了一个自定义注解@RequireRole,加在方法上,通过拦截器统一处理,代码干净又便于维护。
3. 后端核心实现:Spring Boot + MyBatis
3.1 后端项目结构规划
后端代码结构如果不提前规划,写着写着就变成一锅粥了。我实际项目中用的这套结构,可以参考:
com.example.exam ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务层,处理核心逻辑 │ └── impl // 业务实现类 ├── mapper // MyBatis的Mapper接口 ├── entity // 数据库实体类 ├── dto // 数据传输对象,接口出入参 ├── vo // 视图对象,前端需要的数据结构 ├── config // 配置类,比如Security配置、MyBatis配置 ├── utils // 工具类 ├── interceptor // 拦截器 └── common // 通用返回体、异常处理、常量定义这套分层的好处是每一层只负责自己的事,依赖方向是 controller -> service -> mapper,清晰明了。我特别不建议在 controller 里写业务逻辑,哪怕是一个简单的判断。比如判断考试是否开启,这种逻辑应该放到 service 层。刚开始可能觉得多打几个字,后面维护时的酸爽会让你感谢当初的规范。
3.2 难点一:客观题自动评分与主观题人工阅卷
自动评分这块是考试系统的核心技术点。客观题(单选、多选、判断、填空)相对容易,考生提交答案后,后端逐题比对正确答案即可。但有三个坑要提前规避:
第一个坑是多选题的判分规则。全选对才给分,还是少选给部分分?这个规则必须做成配置项,不能写死在代码里。我遇到过的需求有两种:一种严格按照全对才得分,一种多选少选按比例给分。我在exam_paper_question表里加了一个score_type字段来区分,灵活解决。
第二个坑是填空题答案的标准化。考生输入的答案可能多了空格、全半角混用,后端比对时要做字符串清洗,统一去除首尾空格、全角转半角、大小写转换,这些细节不处理好,会出现考生明明答对了却判错的情况,非常影响体验。
第三个坑是主观题的评分流程。简答题、论述题这类没法自动判,需要教师人工阅卷。我的实现方案是:交卷后生成待阅卷列表,教师逐题给分,最后汇总总成绩。这里要注意并发控制,同一份试卷不能同时被两个教师批改,我用 Redis 分布式锁解决了这个互斥问题。
3.3 难点二:考试时间控制与自动交卷
考试时间控制是最容易出事故的地方,我见过太多系统因为时间问题被吐槽。
先说方案,后端统一用服务端时间,前端只负责展示倒计时,不参与最终时间计算。这样规定了几点开考、几点结束,都是以服务器为准,考生本地时间不准也不会影响考试公平性。
倒计时结束自动交卷的实现,我的做法是:
- 前端倒计时归零后立即触发交卷接口;
- 后端接口里做二次校验,判断当前时间是否已经超过考试结束时间;
- 如果前端因为网络异常没调用上交卷接口,后端需要用定时任务扫描超时未交卷的考试记录,强制置为交卷状态,并保存已答题目。
这里定时任务频率要合理,太频繁浪费数据库资源,太稀疏容易出现延迟。我建议按考试的粒度倒排索引,一个考试结束时间到了先更新考试状态,再批量处理考生记录。实际项目中我用的是 Spring 自带的@Scheduled定时任务,每分钟扫一次,配合状态字段推动下一步处理,对中小型考试完全够用。
3.4 难点三:防作弊与防切屏策略
远程考试最大的挑战就是防作弊。技术上能做到的其实是有限的,但该做的策略还是得做。
我实际实现的方案分两层:
前端层面,监听窗口失焦事件,也就是考生切屏或者切应用的时候。一旦检测到切屏,记录日志并计一次警告,超过设定的阈值(比如3次)会自动交卷或锁定考试界面。用 Vue3 的onVisibilityChange监听页面可见性变化就能实现,代码量不大,但威慑力很强。
后端层面,记录考生的登录设备信息、IP地址,提交答案时校验时间间隔是否合理。例如一道题如果在系统里显示用了2秒就答完,大概率是有问题的,可以标记为可疑记录供教师审查。
当然了,这些手段只能提高作弊成本,无法完全杜绝,所以我在系统里把防作弊日志也设计成了独立模块,方便教师后续追溯。
3.5 数据库事务与性能优化
考试系统数据一致性要求很高,比如交卷时要把答题记录和总分一起写入,中间任何一步失败都会造成脏数据。我在核心流程上都加了@Transactional事务控制,保证要么全部成功,要么全部回滚。
MyBatis 这边的性能优化,有几个地方需要特别注意:
- 批量插入。考生交卷时有大量答题记录写入,千万不要循环单条 insert,用 MyBatis 的
foreach批量插入,性能差距是几十倍的。 - 关联查询避免 N+1。获取试卷列表时如果还要逐题查详情,相当于一次考试要发出几十上百条 SQL。我的做法是先把题目ID查出来,再用
IN查询把所有题目一次性加载,内存里组装好返回。 - 合理使用缓存。基本的考试信息、题目内容都是读多写少的数据,用 Redis 缓存起来,QPS 能翻好几倍。涉及选题管理模块的修改时,同步更新缓存即可。
如果这些优化没做,线上压测很容易 CPU 飙高。某次我们压测一个交卷接口,优化前平均响应1.8秒,优化后降到300毫秒以内,差距就是这么明显。
4. 前端核心实现:Vue3
4.1 前端项目结构与工程化配置
前端我用的 Vite + Vue3,编译快,配置简单。项目的目录结构这样划分比较清晰:
src ├── api // 接口请求封装,按模块拆分文件 ├── assets // 静态资源 ├── components // 通用组件 ├── composables // 组合式函数,比如倒计时、答题状态 ├── router // 路由配置 ├── stores // Pinia状态管理 ├── views // 页面级组件 ├── utils // 工具函数 └── styles // 全局样式路由配置上,我把考生和管理员分开,采用不同的布局组件。考生端是简单的考试布局,管理端是带侧边栏的后台框架。路由守卫做登录校验,未登录用户访问考试页面自动跳转登录。管理员页面再套一层角色判断,避免越权访问。
Pinia 做状态管理是 Vue3 生态的标准玩具了,我用它来管理登录用户信息和考试过程中的答题状态。考试页面一打开,把试卷数据拉下来放进 store,各题组件共享同一份答题数据,提交的时候统一收集,效率比组件间来回传值高得多。
4.2 关键页面:登录与考试控制台
登录页算是系统的门面,视觉要清爽,逻辑要严谨。登录的时候把用户名、密码发给后端,成功之后存 Token 和用户信息。我这边特意处理了一个小细节:用户角色不同,登录后跳转的首页不一样,考生去考试控制台,管理员去后台。这个跳转逻辑写在路由守卫里,不需要每个页面自己去判断。
考试控制台页面展示当前用户需要参加的所有考试,每张卡片显示考试名称、开始时间、结束时间、状态。未开始的考试显示“待开始”,进行中的显示“进入考试”按钮,已结束的显示“查看成绩”。这些状态由后端返回,前端按状态渲染不同按钮样式。
进入考试页是整个系统的核心界面。布局分三块:顶部栏(考试信息 + 倒计时)、左侧题目导航、右侧答题区。题目导航可以直观看到哪些题已答、哪些没答、哪些被标记了不确定,方便考生全局把控节奏。这个体验细节对提升系统好感度帮助极大,做的时候不要省。
4.3 倒计时、自动保存与交卷确认
倒计时这块我说说实现细节。Vue3 里用setInterval每秒更新剩余时间,计算出时、分、秒展示。到了最后5分钟,顶部栏变红提醒考生。倒计时归零不再让考生交卷,而是直接触发自动交卷逻辑。
自动保存也是个容易被忽略但很重要的功能。考生答题过程中,每切换一道题,就把当前题的答案暂存到 store。这样即使不小心关掉页面,再次进来还能恢复之前的答案。如果要更强,还可以每30秒自动调一次保存接口,把答案实时存到后端。对网络稳定的场景,我推荐前端 store 暂存 + 定时保存后端接口的双保险方案。
交卷确认弹窗是防误触的最后一道关。点击交卷之后弹出确认框,提示还有哪些题未答,让考生二次确认。确认后调交卷接口,前端进入提交中状态,防止重复点击。接口返回成功后跳转成绩页。
这里有个经验之谈:交卷接口要做幂等性处理,也就是同一个考试 + 同一个考生,重复交卷请求只能成功一次。后端用考试记录的状态字段来判断,如果已经是已交卷就直接返回成功,不能再写入第二遍答案。这个不做的话,网络卡顿引起重复提交会直接导致数据库里数据错乱。
4.4 前后端联调与跨域处理
前后端分离开发,联调是绕不开的坎。跨域问题尤其常见,后端如果没配置 CORS,前端浏览器所有请求都会失败。Spring Boot 里的处理方式很简单,用@CrossOrigin注解或者配置一个全局的CorsFilter即可。
联调的时候我有几个习惯,能少很多破事:
接口统一用全局返回体,前端封装 axios 拦截器解析,避免每写一个接口都要重复处理。开发环境用 Vite 的代理功能把/api前缀代理到后端地址,生产环境则由 Nginx 统一转发。这种方式的好处是前端的代码里永远只需要写相对路径,不会出现写死了后端 IP 到最后部署又要改一遍的情况。
还有一个很容易踩的坑是日期格式。前后端之间传日期,默认格式可能不一样,导致前端解析出来是 NaN。我的方案是统一用时间戳(long 类型)传输,后端转换成日期类型,前端再根据自己的需求格式化。虽然少了一些所谓的雅观,但稳才是第一位。
5. 部署与运维实战
5.1 环境准备与基础配置
考试系统如果要正式投入使用,部署环境不能太寒酸。我常用的配置是:
- 一台 Linux 云服务器(2核4G起步,考试并发高就上4核8G),安装 JDK 17、MySQL 8.0、Redis、Nginx。
- 前端打包成静态文件,用 Nginx 托管并反向代理后端接口。
- 后端打成 jar 包,用 systemd 或 shell 脚本守护进程运行。
数据库初始化这块,把项目里的 SQL 脚本执行一遍,建好库表和初始数据。初始数据里至少要有管理员账号和几个测试用户,方便部署后立刻验证功能。密码字段必须是 BCrypt 加密过的,我项目里提供的是一个初始化脚本自动生成加密密码。
后端配置文件里要区分开发环境和生产环境,Spring Boot 的多环境 Profile 机制很好用。生产环境的数据库密码、JWT 密钥这类敏感信息千万别写死在代码里,用环境变量传入。我吃过这个亏,项目源码泄露导致数据库被删,教训极其深刻。
5.2 从打包到上线的完整流程
上线部署的流程我实际操作了无数次,总结出来一套固定步骤:
第一步,后端执行mvn clean package -DskipTests打包 jar。这一步如果依赖有问题,可以先执行mvn clean install把本地依赖装好。
第二步,前端执行npm run build,生成dist目录,里面的静态文件就是最终产物。构建前记得改.env.production,把接口的 baseURL 配置成生产环境域名路径。
第三步,把 jar 包上传到服务器,用java -jar exam-system.jar启动。正式环境我会写一个start.sh脚本,脚本里带上-Xms512m -Xmx1024m之类的 JVM 参数,避免默认参数不合适导致性能问题。
第四步,前端静态文件上传到 Nginx 的 html 目录,配置 Nginx 站点,把 API 请求反向代理到后端端口。
第五步,验证环境。用管理员账号登录后台,创建一个测试考试,用两个账号模拟考生考试、交卷、阅卷、查分,全流程走通才能算上线成功。
5.3 Nginx 配置要点与 SSL 证书
Nginx 配置是部署环节比较关键的一步。下方是我项目里的核心配置片段,注释已经写得比较详细了:
server { listen 80; server_name exam.example.com; # 前端静态资源 root /usr/share/nginx/html; index index.html; # 解决 Vue Router 的 history 模式刷新 404 问题 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }上 HTTPS 证书现在基本是标配了,不然考试过程中浏览器会提示不安全,影响严肃性。我用的方案是让 Nginx 监听 443 端口,配置好证书文件路径。证书申请挂在域名服务商或云平台后台就行。
这里要特别提醒一下:proxy_pass末尾的斜杠很重要,http://127.0.0.1:8080/表示把/api前缀替换掉,后端收到的就是不带/api的原生路径。如果忘写这个斜杠,路由会多出一截,接口全部 404,排查起来非常迷惑人。
6. 常见问题与排查技巧实录
6.1 常见问题速查表
我把实际项目开发中排查过的典型问题整理成一张速查表,仅供参考:
| 问题现象 | 可能原因 | 排查思路与处理 |
|---|---|---|
| 前端登录时报跨域错误 | 后端未配置 CORS 或代理配置错误 | 先看浏览器 Network 里的请求头,确认是否真的跨域;后端配置全局 CorsFilter 即可 |
| 交卷后成绩一直为0 | 答题记录未正确入库或评分逻辑未执行 | 查后端日志,确认交卷接口是否完整走完流程,重点看事务是否回滚 |
| 考生同时在线时接口变慢 | 数据库连接池过小或 SQL 效率低 | 调大 HikariCP 连接池配置,用慢查询日志找出低效 SQL,优化索引 |
| 倒计时与服务器时间不一致 | 前端本地时间与服务器时间不同步 | 前端只做展示,后端判断时间,接口返回服务器当前时间戳 |
| 刷新页面后答题记录丢失 | 前端未做答案暂存或保存接口未调 | 每次切换题目时保存答案,刷新前把 store 数据持久化 |
| 自动交卷延迟 | 定时任务执行频率太低或扫描逻辑太重 | 调整@Scheduled频率到合理区间,按考试批次批量处理 |
6.2 试卷发布与考试并发的实战压力测试
上线前压测是必须做的。我用 JMeter 模拟过在线考试的并发场景:200个线程同时进入考试页面、随机答题、同时交卷。第一次跑出来的结果惨不忍睹,交卷接口大量超时。排查下来发现瓶颈在数据库连接池,默认配置10个连接根本扛不住。
调整 HikariCP 配置,maximum-pool-size设为50,同时给交卷接口加上读写分离的优化,重测结果稳定在单次请求500毫秒以内。如果你的考试规模更大,建议加一层 Redis 缓冲,答题数据先写 Redis 再异步落库,吞吐量直接上几个档次。
压测还有一个作用:检验自动交卷逻辑在极端情况下的正确性。我测试的时候发现,如果交卷请求和自动交卷定时任务同时触发,会产生重复写记录的问题。后面我在业务层加了幂等校验,先查考试记录状态,再做后续操作,问题彻底解决。
6.3 我踩过的几个典型坑
这里单独说一下我开发过程里印象最深刻的几个坑,希望你少走弯路。
第一个坑是 MyBatis 的一对多查询。获取试卷列表时需要把题目列表也带出来,我一开始在 XML 里用<collection>做嵌套映射,结果测试发现同一条考试记录被查出了多行。原因是 MyBatis 的嵌套结果映射在分页或者有重复 ID 的情况下,容易出现结果集翻倍。我的解决方式是把题目查询拆成独立查询,在 service 层进行数据组装,虽然多写几行代码,但逻辑完全可控,不会出现诡异问题。
第二个坑是数据库连接在考试高峰期被打满。当时系统里新增了一个定时任务,用于自动清空过期验证码,结果这个任务每秒钟跑一次,每次占用一个连接。数据库本来就只有30个连接,一高峰期直接全部被定时任务抢走,考生请求全部排队。后来把定时任务改成每分钟跑一次并加上分布式锁,问题就消失了。排查过程非常痛苦,最后是靠数据库连接监控工具看到了连接被谁占用才定位到。
第三个坑是前端请求超时时间设置太短。交卷接口如果考生答案特别多(比如有几十道简答题),传输时间可能比较长,axios 默认超时时间是10秒,容易超时导致界面报错。我把交卷接口单独设置了较长的超时时间,其他接口维持默认值,兼顾体验和稳定性。
6.4 数据备份与安全策略
考试系统的数据不能丢,备份策略必须提前设计好。我实际部署的时候做了两层:每天凌晨全量备份一次 MySQL 数据,同时开启 binlog 实时记录增量变更。这样如果某天误删数据,可以从备份中恢复,数据丢失最多只丢一天;再配合 binlog,可以把恢复精确度提到某个分钟级别。
安全方面有几件事必须做:
- 密码不能明文存储,统一用 BCrypt 加密,即使数据库泄露,攻击者也拿不到明文密码。
- 登录接口限制频率,比如一分钟最多试5次,防止暴力破解。
- 考试详情的发布和修改要记录操作日志,出了问题能追溯。
- 后端接口做参数校验,防止 SQL 注入和异常数据。MyBatis 里规范使用
#{}预编译占位符,不要用${}拼接用户输入。
写在最后的个人体会
做完这套考试系统,我最大的感受是:业务逻辑清晰的 CRUD 项目,反而是最考验工程素养的。考卷设计、时间控制、并发写入、数据一致性,每一个环节单独拿出来都不难,但合在一起,任何一个小细节出问题,都会直接砸在真实考试的现场。
我个人在实际操作中的体会是,这个项目的核心价值不在于技术栈多炫,而在于你如何把线上考试这件严肃的事做得可靠。客观题自动评分规则做配置化、交卷接口做幂等、数据库表冗余考试快照、前端倒计时只做展示不做裁决,这些新手容易忽略的细节,才是决定系统质量的分水岭。
如果你正准备用 Spring Boot + Vue3 做类似的项目,建议先花时间把数据库设计这关过了,再动手写业务代码,70% 的坑其实在设计阶段就能避开。最后再分享一个实用小技巧:本地开发的时候可以用 Docker 跑 MySQL 和 Redis 的环境,一行命令搞定依赖,省去手工安装配置的麻烦,开发效率会快很多。