ARTICLE DETAIL

资讯详情

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

Java在线考试系统源码实战:从环境搭建到防作弊改造

Java在线考试系统源码实战:从环境搭建到防作弊改造 简介这是一份 Java 语言实现的在线考试系统源码后端整合 Spring MVC、MyBatis、FreeMarker前端结合 Bootstrap、jQuery 与 Vue.js 完成页面渲染和数据交互适合正在完成课程设计、毕业设计或希望了解传统 Java Web 与前端 MVVM 整合方式的开发者。压缩包共包含 332 个文件其中 Java 类 47 个、HTML 页面 22 个、CSS 样式 69 个、JavaScript 脚本 58 个还包含 SQL 数据库脚本、XML 工程配置、项目说明文件等压缩后大小 10.33MB整体结构按照后端代码、前端页面、静态资源等进行了分类目录层级明确便于快速定位功能模块对应的代码或样式文件。从文件结构看项目覆盖了在线考试常见的用户、试题、考试、成绩等业务模块代码分层相对清晰并保留了数据库初始化脚本可快速搭建可运行环境同时便于在此基础上扩展新功能通过查看 Java 源码与前端页面可以较好地理解 Spring MVC 的请求处理流程、MyBatis 的映射配置以及 Vue.js 与后台的数据交互方式。这份源码包已有 1111 人学习/下载对于需要现成 Java Web 项目参考的开发者来说能够节省从零搭建框架的时间具有较强的实用价值。1. 这份Java在线考试系统源码能撑起课设答辩也能做小型上机考试工具很多同学下载这份 Java在线考试系统源码 的动机我太清楚了课程设计或毕业设计需要一个能演示、能答辩、能写进报告的完整项目也有小团队想拿它做单位内部的上机考试系统。无论哪种你真正要解决的都是同一件事线上考试不是发一份网页问卷那么简单它要同时管住题库、组卷、答题、判分、成绩这五个环节还得考虑学生中途切走页面、交卷丢成绩这些细节。这套源码包的价值不在某个功能多惊艳而在于角色分明、流程闭环。你拿到手要做的事也很清晰先把环境跑通再把代码链路读懂然后把默认题库换成你自己的最后把几个致命坑堵上。下面按这个顺序一步步来新手能跟得动熟手也能直接跳到第5章看排错。2. 把zip变成本地能跑的项目JDK、Maven、MySQL版本匹配与启动命令先把丑话说在前面这类源码包能不能跑起来九成取决于环境而不是代码。忘掉“直接双击运行”的幻想。按下面的顺序走一遍能避掉80%的环境坑。2.1 解压与目录识别先花两分钟判断这是什么架构拿到zip先别急着扔进IDEA。第一步是解压然后把目录结构看清楚。unzip Java在线考试系统源码.zip -d exam-system cd exam-system tree -L 2 -d看输出结果就能判断架构有src/main/java和resources/application.yml一般是Spring Boot单体应用自带Maven构建如果出现src/main/webapp多半是老式JSPServlet项目要部署到外部Tomcat而不是直接run还有一种前后端分离版本前端目录放的是Vue或Layui的静态资源后端是纯接口工程这种会多一层目录。不要急着改代码先确认类型再选启动方式这是血泪经验。tree -L 2 -d表示只列两层目录、只看文件夹。不加-d会把target里的class文件全部刷出来干扰判断。接着看根目录有没有README或sql目录ls -la ls sql/sql目录里一般放着建库脚本exam.sql或init.sqlREADME里通常会写默认账号。如果都没有就去resources下找application.yml看数据库配置里的库名。这一步很关键因为很多老脚本的默认账号是admin/123456而不是你猜的root。2.2 JDK与Maven匹配一个报错就把你卡半小时这类源码绝大多数跑在JDK 1.8上少数近两年的版本用JDK 17。先确认你本机的版本和项目要求是否一致。java -version mvn -v grep -A 3 parent pom.xml | head -20java -version看当前PATH下的JDKmvn -v里也有一行Java version那是Maven实际使用的JDK两者必须一致否则编译时会出现“一个JDK编译、另一个JDK运行”的怪问题。最后一条命令看pom.xml里的Spring Boot父工程版本Spring Boot 2.x要求JDK 1.8Spring Boot 3.x要求JDK 17。课程设计源码包里九成是2.x如果你电脑装的是17后面那个“源发行版 17”的报错就在等你了。老项目不要硬升JDK装回1.8最省事。再配Maven镜像。国内直接拉中央仓库会卡到怀疑人生配置在~/.m2/settings.xml里。mirror idaliyunmaven/id mirrorOf*/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror这段写在mirrors节点内。mirrorOf写*表示所有依赖都走这个镜像代价是某些冷门依赖镜像里没有再换回中央仓库。id随便起url建议用public聚合仓它同时代理了central和jcenter。判断点老式课程设计近两年工程化项目构建方式JSP Servlet TomcatSpring Boot Maven常见JDK1.81.8 / 17数据库脚本exam.sql 手动导入init.sql 初始化数据前端形式JSP页面Thymeleaf / Layui / Vue2.3 数据库初始化建库、导脚本、统一UTF8MySQL 5.7或8.0都能跑关键在驱动和连接串。先手动建库再导脚本比直接在脚本里改库名更可控。mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS exam_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p exam_db sql/init.sql第一条命令把库建好字符集直接指定utf8mb4避免后面中文乱码。第二条把脚本导进去。注意看导入过程有没有报错特别是外键约束报错——很多脚本建表顺序是乱的被引用表还没建就执行了外键语句这种情况把建表语句按依赖顺序手动调整或者干脆把外键那行注释掉。然后改数据库配置。老源码包的项目名五花八门但配置文件的字段基本一样spring: datasource: url: jdbc:mysql://localhost:3306/exam_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver逐个说参数characterEncodingutf8保证JDBC读写中文正常serverTimezoneAsia/Shanghai解决MySQL 8下的8小时时差allowPublicKeyRetrievaltrue是MySQL 8的caching_sha2_password认证插件需要的不加会报Public Key Retrieval is not allowed。如果driver-class-name写的是com.mysql.jdbc.Driver说明依赖里是MySQL 5.x驱动连5.7没问题连8.0建议换成上面这个cj驱动。2.4 启动与登录验证看到登录页才算跑通Spring Boot项目直接打包启动。mvn clean package -Dmaven.test.skiptrue java -jar target/exam-*.jar第一行跳过测试打包课程设计源码里的测试类经常写坏跳过能少报一堆错。第二行用jar方式启动绕开IDEA里各种花式配置差异。看到Started ExamApplication in x.x seconds说明服务起来了。老式JSP项目则要打war包丢进Tomcat的webapps目录启动Tomcat后看日志不再报错再访问。打开浏览器输入http://localhost:8080如果配置了server.servlet.context-path就在后面加路径比如/exam。默认账号别在登录页瞎猜去数据库里查最准——这一步的具体SQL在第4章。提示8080端口被占用时先lsof -i:8080看是谁占的再决定改端口还是杀进程。这是环境问题不是项目问题。3. 读懂核心模块的三个链路登录鉴权、随机组卷、自动判分跑通只是起点。想答辩时讲清楚原理想把系统改造成自己的必须读懂代码里的三条主链路。按业务顺序来登录、组卷、答题判分。3.1 登录鉴权三套角色怎么区分session里存了什么登录模块的Controller逻辑一般是这个套路PostMapping(/login) public Result login(RequestBody LoginRequest req, HttpSession session) { User user userMapper.findByUsername(req.getUsername()); if (user null || !passwordEncoder.matches(req.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } session.setAttribute(loginUser, user); session.setAttribute(role, user.getRole()); // 1管理员 2教师 3学生 return Result.ok(user); }这里做了两件事一是查用户后比对密码摘要二是把用户对象和角色写进session。密码比对用passwordEncoder.matches()而不是把数据库密码取出来equals是因为表里存的是MD5或BCrypt摘要原始密码不可见。你应该顺着代码找一下拦截器一般有个LoginInterceptor检查session里有没有loginUser没有就重定向到登录页。角色值别靠记去看SysRole枚举或user表字段注释。三个角色的功能边界是学生进考试中心和成绩查询教师进题库和阅卷管理员进系统设置和考试发布。前端靠登录返回的role字段控制菜单显示后端接口再校验一次权限——两层都做缺一不可只靠前端隐藏菜单等于裸奔。3.2 随机组卷为什么考试开始前要把试卷“落库”组卷分两种固定试卷和随机组卷。固定试卷就是试卷题目表里一行行把题目选好随机组卷只存规则考试发布时按规则抽题。抽题时机的选择是代码里比较讲究的点。简单实现是学生点“开始考试”那一刻临时抽题这样每个学生拿到的题都不一样。常见做法是考试发布后由管理员触发为每个学生生成一份独立试卷并落库。核心方法大概长这样public void assignPaperToStudent(Integer examId, Integer studentId) { ListPaperRule rules paperRuleMapper.findByExamId(examId); for (PaperRule rule : rules) { ListQuestion questions questionMapper.randomPick( rule.getSubjectId(), rule.getQuestionType(), rule.getQuestionCount()); for (Question q : questions) { paperQuestionMapper.assignStudentQuestion( examId, studentId, q.getId(), rule.getScore()); } } }两层循环外层遍历组卷规则比如单选20题、多选10题、简答4题内层按规则随机抽题并写入试卷题目表。关键点随机抽题发生在考试开始前而不是交卷时。这样学生交卷后试卷是冻结的复查时能看到自己当时抽到的原题。如果抽题放在查询时临时拼会出现“查成绩时题目变了”的离谱事故。配套的SQL是抽题核心select idrandomPick resultTypeQuestion SELECT * FROM question WHERE subject_id #{subjectId} AND question_type #{questionType} AND status 1 ORDER BY RAND() LIMIT #{count} /selectORDER BY RAND()在小数据量时足够简单上万条题目时性能很差因为MySQL会对全表排序。做课设无所谓拿去支撑真实考试就要改成先查最大id再用随机偏移量定位。status 1表示只抽启用状态的题目避免把草稿题发给学生。3.3 自动判分客观题机改主观题不要硬判判分逻辑是答辩高频考点。原则是单选、多选、判断交给程序比对填空题可以按文本精确匹配简答题必须留给教师人工阅卷。强行用关键词匹配判简答题会在答辩时被老师问住。public void judgeObjective(ListAnswerDetail details) { for (AnswerDetail d : details) { Question q questionMapper.findById(d.getQuestionId()); if (q.getQuestionType() 3) { boolean correct q.getAnswer().equalsIgnoreCase(d.getStudentAnswer()); d.setScore(correct ? d.getFullScore() : 0); } else { d.setStatus(2); // 待阅卷 } answerDetailMapper.update(d); } }这段能跑的前提是题库表里answer字段存的是规范格式单选题存“B”多选题存“ABD”判断题存“T”或“F”。比较时用equalsIgnoreCase是个细节不然学生填小写b会被误判。多选题最坑学生答案顺序和标准答案不一致时直接equals会判错比较前最好把字符串拆成List再containsAll。题型判分方式存储格式单选程序比对A/B/C/D多选程序比对需处理乱序ABD判断程序比对T/F填空程序精确匹配文本简答人工阅卷文本得分字段3.4 考试记录与成绩表先落库还是先返回交卷接口最容易写错。做过一次你就会记住成绩更新必须先落库再返回前端而且要用事务。一次事务内完成更新考试记录表的状态和总分、批量写入答题明细、提交事务。如果先把结果算出来返回给前端再异步落库用户一关浏览器下次查成绩就是0分。这个黑匣子问题每年都有人答辩翻车。记录状态一般用数字字段表示状态值含义对应操作人0考试中学生答题1待阅卷教师改主观题2已完成学生查成绩exam_record表负责总账谁考的哪场、多少分、什么状态answer_detail表负责明细每道题学生答了什么、得了多少分。两表共用exam_id和user_id关联。重点是时序插入record → 插入detail → 更新score三件事必须在一个Transactional方法里缺一个都可能导致“总分对不上明细分”。4. 把默认题库改造成你自己的核心表结构、初始化SQL与数据隔离跑通和看懂之后真正的活开始了把项目里自带的示例数据换成你自己的题目和科目。这一步决定答辩时老师问你“这套系统你自己改了多少”时的底气。改之前的核心原则是先备份原库再动手避免把联军数据搞坏。4.1 核心表关系一张图拆成文字这类考试系统的表结构大同小异核心表就六张用户表、科目表、题库表、考试表、试卷关联表、考试记录表。想快速定位在SQL目录里搜grep -n CREATE TABLE sql/init.sql一次性列出所有建表语句很快能看到哪些是主业务表。常见命名sys_user、subject、question、exam、paper_question、exam_record、answer_detail。如果脚本分散在多个.sql文件里就逐个grep。表之间的关联靠外键或逻辑字段用户表用role区分身份题库表通过subject_id关联科目考试表通过exam_id挂接试卷试卷表通过paper_question表把题目关联进来考试记录表通过exam_id和user_id记录“哪场考试哪个考生”。在纸上把这几个关联画一遍代码里的Mapper就全对上了。4.2 建表细节考试记录表必须有的唯一索引特意单独看建表语句这是数据安全里最容易被忽略的一环CREATE TABLE exam_record ( id bigint NOT NULL AUTO_INCREMENT, exam_id bigint NOT NULL COMMENT 考试ID, user_id bigint NOT NULL COMMENT 考生ID, score int DEFAULT 0 COMMENT 总分, status tinyint DEFAULT 0 COMMENT 0考试中 1待阅卷 2已完成, submit_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_exam_user (exam_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;UNIQUE KEY的意思是同一个用户在同一场考试只能有一条记录。没有这个约束学生多点几次交卷就可能出现多条记录成绩展示时不知道取哪条甚至第二次提交的0分覆盖第一次的真实得分。这是这类系统最常见“成绩丢失”事故的根源。前端做防重复点击只是体验优化数据库唯一约束才是后悔药。4.3 初始化题库往question表插自己的题换掉示例数据最直接的方式是先看题目表字段再按字段插入。INSERT INTO subject (subject_name, teacher_id) VALUES (Java核心基础, 2); INSERT INTO question (subject_id, question_type, content, option_a, option_b, option_c, option_d, answer, difficulty, score, status) VALUES (1, 1, 下列哪个是Java的基本数据类型, String, int, Integer, Object, B, 1, 2, 1);这是最朴素的单条插入。实际改造时不要手写几千条SQL常见做法是先在Excel里整理题目再用脚本批量生成INSERT或者用Navicat的导入功能。注意答案字段和判分逻辑强相关错一个字符学生就白考了。插入前确认subject_id对得上插完跑一句检查SELECT q.id, q.content, q.answer, s.subject_name FROM question q JOIN subject s ON q.subject_id s.id LIMIT 20;这条查询把题目和科目串起来看有没有题目挂到不存在的科目下。如果出现脏数据随机组卷时抽题SQL会静默过滤掉试卷题量不足更麻烦。4.4 数据隔离教师只能出自己科目的题改题库前先想清楚权限。如果只有一个管理员账号那无所谓但系统里有教师角色就必须考虑A老师能不能看到B老师的题。常见做法是加一张关联表CREATE TABLE teacher_subject ( id bigint NOT NULL AUTO_INCREMENT, teacher_id bigint NOT NULL, subject_id bigint NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_teacher_subject (teacher_id, subject_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表表达“教师和科目的多对多授权关系”。题库列表查询时在SQL里join它只返回该教师有权限的科目下的题目。没有这张表的简化源码通常会在question表加creator_id字段按创建人过滤。两种都行但关联表更灵活——一个老师可以带多门课。答辩时主动讲出这层数据隔离设计比堆功能更拿分。对应查询的Mapper改造SELECT q.* FROM question q INNER JOIN teacher_subject ts ON q.subject_id ts.subject_id WHERE ts.teacher_id #{teacherId} ORDER BY q.id DESC#{teacherId}要从登录用户的session里取不要信任前端传参。有些源码的坑是登录后不存用户id查询时让前端把teacherId作为参数传进来接口一旦被调用者知道参数名就能看别人的题。至少要做到教师id从服务端会话取。5. 跑不起来和成绩不靠谱5个高频问题的排查记录这一章写给那些对着报错日志发愁的同学。每个问题都是实际见过的翻车场景按“现象→原因→解决”三步写直接对照着排查。5.1 启动即报“源发行版 17 需要目标发行版 17”现象mvn编译或IDEA构建时报java: invalid source release: 17或“源发行版 17 需要目标发行版 17”。原因当前JDK是17而项目本身是Spring Boot 2.x、为JDK 1.8写的反过来也可能项目要求高版本JDK但机器是1.8。本质是JDK版本和编译参数对不上。解决先确认项目的Spring Boot版本。如果是2.x直接装JDK 1.8并把三个位置切过去IDEA的Project Structure里Project SDK、Maven Runner的JRE、系统环境变量JAVA_HOME。同时改pomproperties java.version1.8/java.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties这三组配置分别控制Spring Boot插件、Maven编译源码版本、编译目标字节码版本。统一成1.8后一般就好。如果项目是Spring Boot 3.x那只能换JDK 17因为3.x在字节码层面就不支持1.8光改java.version糊弄不过去运行时照样报错。5.2 Maven依赖下载不动卡在Downloading现象编译时一直卡在Downloading https://...最后报Could not resolve dependencies。原因默认中央仓库从国内访问不稳定或本地仓库里缓存了损坏的lastUpdated标记文件。解决先配阿里云镜像然后清理本地仓库标记文件find ~/.m2/repository -name *.lastUpdated -deleteMaven下载失败后会在本地仓库留下*.lastUpdated空文件下次构建时看到这个文件就认为依赖已存在直接跳过下载于是越报越错。删掉后重新mvn clean package即可。如果公司内网有私服把settings.xml里的mirror换成私服地址。判断依赖是否下载完整看~/.m2/repository里对应groupId目录下有没有_remote.repositories文件。5.3 页面中文全是问号现象登录页和题目页面中文正常但提交的答案、新增的题目在页面上显示成???或乱码。原因三个环节至少一个没统一字符集——页面文件编码、数据库表字符集、JDBC连接串。解决按顺序排查。先看页面源文件编码老项目很多是GBK用IDEA打开文件右下角转为UTF-8再看数据库表字符集执行SHOW CREATE TABLE question确认是utf8mb4最后确认JDBC url里有characterEncodingutf8。三段统一后重启。注意如果数据已经以错误编码写入数据库改完配置后乱码依旧说明写进去的字节本身就是错的。先备份数据再重导一次别指望改配置能修复历史脏数据。5.4 交卷成功成绩为0或重复提交记录丢失现象学生交卷前端提示成功教师端看成绩是0或者同一学生考试记录出现多条状态混乱。原因交卷接口没有做幂等。第一次交卷正常第二次交卷带空答案再次进入判分逻辑把score更新成了0或者表上没有唯一索引插入了多条record。解决先加唯一索引再在Service层做“先查后判”。ALTER TABLE exam_record ADD UNIQUE KEY uk_exam_user (exam_id, user_id);ExamRecord old recordMapper.findByExamIdAndUserId(examId, userId); if (old ! null old.getStatus() ! 0) { throw new BizException(已交卷请勿重复提交); }唯一索引是数据层兜底代码判断是业务层提示两者同时做才稳。注意判断条件是status ! 0而不是“记录存在就拒绝”因为考试中途可能有自动保存的进行中记录状态是0这种要放行。5.5 考试时间对不上整体差8小时现象管理员设置9点开考数据库里看着也是9点学生端到点却进不去日志显示服务器时间是凌晨1点。原因JVM默认时区、MySQL连接时区、操作系统时区三者不一致最常见是连接串没写serverTimezone驱动用UTC时区和东八区正好差8小时。解决改连接串是最快的url: jdbc:mysql://localhost:3306/exam_db?serverTimezoneAsia/ShanghaiuseLegacyDatetimeCodefalse加了参数后驱动按东八区解析时间。如果还不对检查Linux服务器本身的时区。Windows本地跑一般不犯这个错这个坑多出现在部署到服务器之后部署时用timedatectl set-timezone Asia/Shanghai把系统时区一起修正。数据库里datetime字段本身不带时区靠的是客户端解析时指定时区这就是这个参数最核心的作用。6. 从“能跑”到“能交差”验收清单、防作弊改造与一个技巧6.1 先按一条完整链路验收功能验收别停留在“能打开首页”。按真实流程走一遍管理员建考试并指定试卷规则教师出一题并发布学生进入考试、刷新一次页面再提交教师给主观题打分学生端查到最终成绩。这条链路每步截图按时间排好答辩时比十张登录页截图有用。再多测一个边界场景考试结束前1分钟交卷再重进系统确认记录状态是已交卷。6.2 防作弊最小改造老源码大多只做前端倒计时改系统时间就能续考。最小改造是把判时逻辑放到后端Scheduled(cron 0 * * * * ?) public void autoSubmitTimeoutExams() { ListExamRecord timeoutRecords recordMapper.findTimeoutRecords(); for (ExamRecord record : timeoutRecords) { submitService.forceSubmit(record.getExamId(), record.getUserId()); } }这个定时方法每分钟扫一次考试记录把超过结束时间还处于考试中的记录强制交卷。cron表达式0 * * * * ?表示每分钟第0秒执行对数据库只做一次范围查询压力很小。学生改本地时间也没用判断依据全在服务器。6.3 一个教训当年做课设偷懒用了纯前端倒计时结果同学把电脑时间改慢又考了一轮被指导老师在演示现场当场点破场面很难忘。后来我把时间校验全挪到后端又给exam_record加了唯一索引。这份源码里最值得你花时间的就这两个位置判时和后端防重。它俩同时解决“超时交卷”和“重复交卷”两个致命问题改完你就超出大多数课设水平了。希望帮到你。本文还有配套的精品资源点击获取
返回列表