本文还有配套的精品资源,点击获取
简介:直接拿来就能跑的Java在线考试系统,专为计算机专业毕业设计准备。用SpringBoot搭建,前后端代码齐全,数据库用MySQL,附带auth.sql和exam.sql两个建库脚本,开箱即用。部署文档.docx写得特别细,从环境配置、依赖安装到启动命令全都有,本地调试三步就能搞定。系统拆成auth-service(登录注册权限)、exam相关模块(出题、组卷、考试、阅卷)、monitor-service(服务状态监控)和config-server(统一配置管理),结构清晰,方便答辩时讲清楚模块分工。资源包里还有说明.txt快速上手提示、backup.txt记录版本变更、pom.xml多份确保Maven构建稳定,前端资源放在app/和fileServer/目录下,测试配置karma.conf.js也一并提供。所有代码已在标准JDK8+、Maven3.6+、MySQL5.7环境下验证通过,不改代码也能演示完整流程,适合课程实践、中期检查或最终答辩。
1. 这不是“又一个Demo”,而是一套能扛住答辩拷问的毕设系统
你是不是也经历过:花两周搭了个SpringBoot项目,跑起来能登录、能跳转、能查个列表,结果导师一问“这个服务怎么注册到Nacos?”、“考试并发时怎么防重复提交?”、“阅卷结果怎么实时推给学生?”,当场卡壳,PPT翻到一半就冷场?我带过六届毕设,每年都有至少三四个学生倒在“能跑”和“能讲清楚”之间——代码是别人的,逻辑是模糊的,部署是蒙的,答辩像背课文。这套SpringBoot在线考试系统,就是为解决这个问题生的。它不是教学Demo,也不是半成品模板,而是我在高校实验室陪三届学生打磨出来的答辩友好型工程实践样本。关键词里写的“SpringBoot考试系统”“Java毕设源码”“MySQL考试数据库”,每一个都不是虚词:auth-service里JWT令牌刷新机制写在AuthController.java第217行,有注释说明为什么用Redis双token而非单token;exam.sql建表时exam_paper的status字段用了TINYINT(1)而非ENUM,因为MySQL 5.7对ENUM迁移不友好,答辩现场改库更稳妥;deploy.sh脚本里mvn clean package -Dmaven.test.skip=true加了-Dmaven.test.skip=true而不是-DskipTests,这是Maven3.6+的兼容写法,避免答辩机上因版本差异打包失败。它不追求炫技,但每个模块都经得起“为什么这么设计”的追问。适合谁?计算机/软件工程专业大四学生,尤其适合那些想把毕设做成“真实小系统”而非“CRUD流水线”的人——你不需要从零造轮子,但能清晰说出每个jar包的作用、每条SQL的设计意图、每次HTTP请求背后的服务流转。本地调试三步启动?那是底线;答辩时被问“如果监考老师同时批100份卷子,数据库会不会锁表?”,你能打开ExamMarkService.java指着@Transactional(isolation = Isolation.READ_COMMITTED)解释隔离级别选择依据,这才是这套源码真正的价值。
2. 系统整体架构与模块拆解:为什么这样分?分给谁看?
2.1 四模块协同不是为了“高大上”,而是为了答辩逻辑自洽
很多毕设系统把所有功能塞进一个spring-boot-starter-web里,答辩时讲“用户管理”“考试管理”“成绩统计”三个模块,导师立刻追问:“这三个模块数据怎么交互?事务怎么控制?接口怎么鉴权?”——然后你就得临时翻代码找@Transactional位置。这套系统强制拆成auth-service、exam-service、monitor-service、config-server四个独立Maven子模块,根本目的不是微服务化,而是让答辩陈述有清晰的叙事主线。我把它比作做菜:auth-service是厨房的门禁系统(只管谁有钥匙进门),exam-service是灶台和厨师(专注炒菜——出题、组卷、考试、阅卷),monitor-service是墙上的温度计和油烟报警器(监控火候和安全),config-server是调料柜的标签(所有盐、糖、酱油放在哪,统一贴标)。它们之间不直接“握手”,全靠config-server下发配置、auth-service签发的JWT令牌传递身份、monitor-service通过Actuator端点采集指标。这种松耦合不是为技术而技术,而是让你答辩时能一句话说清边界:“老师,登录注册权限由auth-service独立负责,它不碰任何考试数据;exam-service拿到合法令牌后,只处理考试业务逻辑,它的数据库连接池、事务配置、缓存策略全部独立配置,和认证模块物理隔离。”——这比“我用SpringSecurity做了权限”有力得多。
2.2 模块职责与技术选型背后的“答辩话术”
每个模块的技术选型都预留了答辩提问点,且答案已内嵌在代码和文档中:
auth-service(认证服务):
用Spring Security OAuth2而非JWT纯手写,因为OAuth2是行业标准协议,答辩时可引用RFC6749说明授权码模式的安全性;Redis存储refresh token而非内存,RedisTemplate配置了setEx过期时间(见AuthConfig.java),解释“防止长期令牌泄露”;密码加密用BCryptPasswordEncoder(12),12是成本因子,在application.yml里明确写出,方便回答“为什么选12不选10或14”。exam-service(考试服务):
题库用MySQL而非MongoDB,exam_question表设计difficulty_level TINYINT(1) CHECK (difficulty_level IN (1,2,3)),答辩时可强调“结构化查询需求强,关系型数据库更适合多表关联统计”;自动阅卷部分,客观题用HashMap<String, String>预存答案(AnswerBank.java),主观题留空待人工批阅,体现“人机协同”设计思想;@Scheduled(cron = "0 0 2 * * ?")定时清理过期考试记录,cron表达式含义在ExamScheduler.java注释里逐位解释,避免答辩时被问懵。monitor-service(监控服务):
不用Prometheus+Grafana堆大屏,只集成Spring Boot Actuator的/actuator/health、/actuator/metrics、/actuator/threaddump三个端点,application.yml里配置management.endpoints.web.exposure.include=health,metrics,threaddump,理由很实在:“毕设答辩环境无K8s集群,轻量级端点足够验证服务存活、资源占用、线程阻塞等核心健康状态。”config-server(配置中心):
用Spring Cloud Config Server搭配Git后端(application.yml里spring.cloud.config.server.git.uri=file:///path/to/config-repo),而非Apollo或Nacos,因为Git路径本地可模拟,答辩时能快速演示“修改数据库密码后,重启config-server,其他服务自动拉取新配置”,证明配置动态生效能力——这比讲原理更有说服力。
提示:答辩前务必在
doc/部署文档.docx里找到对应章节,把每个模块的“为什么选这个技术”“为什么这么配置”抄到答辩提纲里。导师不会考你源码细节,但会考你设计决策的合理性。
2.3 数据库设计:两个SQL脚本里的“隐藏考点”
auth.sql和exam.sql不是简单建表,而是埋了答辩高频问题:
auth.sql中sys_user表的username字段设为UNIQUE且NOT NULL,但phone字段允许NULL,email字段设UNIQUE——这引出问题:“为什么手机号可以为空,邮箱却要唯一?”答案在说明.txt第3行:“高校学生注册优先用学号,手机号作为备用联系方式,邮箱用于找回密码,需保证唯一性以防账号劫持。”exam.sql中exam_paper(试卷表)和exam_paper_question(试卷题目关联表)采用逻辑删除(is_deleted TINYINT(1) DEFAULT 0),而非物理删除。ExamPaperMapper.xml里所有查询SQL都加了AND is_deleted = 0,更新SQL用UPDATE exam_paper SET is_deleted = 1 WHERE id = #{id}。答辩时若被问“如何保证历史试卷数据可追溯?”,直接指向此处——逻辑删除保留数据完整性,符合教育系统审计要求。exam_question(题库表)的content字段用TEXT而非VARCHAR(2000),analysis(解析字段)同样用TEXT。说明.txt解释:“题目内容含公式、代码片段、图片base64编码,长度不可控,TEXT类型避免截断风险。”
这些设计细节,文档里写了,代码里实现了,答辩时你只要翻开sql/目录下的SQL文件,手指着对应行,就能把“为什么”讲得扎实。
3. 核心细节解析与实操要点:本地调试三步走,每步都是考点
3.1 环境准备:JDK8+、Maven3.6+、MySQL5.7——不是口号,是避坑清单
别跳过这一步!我见过太多学生在答辩前夜才发现环境不匹配:
JDK版本陷阱:
pom.xml里<java.version>1.8</java.version>明确锁定JDK8,但很多同学装的是JDK11或17。mvn -v输出必须显示Java version: 1.8.0_XXX。若报错Unsupported class file major version 61(JDK17编译),说明本地JDK太高;若报错java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext(JDK9+移除JAXB),说明JDK太低。解决方案:下载jdk-8u202-windows-x64.exe(Windows)或jdk-8u202-macos-x64.dmg(Mac),安装后配置JAVA_HOME指向JDK8根目录,并在cmd或Terminal中执行java -version确认。Maven版本校验:
pom.xml里<maven.compiler.source>1.8</maven.compiler.source>和<maven.compiler.target>1.8</maven.compiler.target>要求Maven3.6+。mvn -v输出中Apache Maven版本号必须≥3.6.0。旧版Maven(如3.0.5)会报错Unknown lifecycle phase "clean"。升级方法:去https://maven.apache.org/download.cgi下载apache-maven-3.6.3-bin.zip,解压后配置MAVEN_HOME和PATH,重启终端再验证。MySQL5.7兼容性:
exam.sql里CREATE TABLE exam_paper ( ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;指定了utf8mb4字符集,这是为了支持emoji和四字节UTF8字符(如某些数学符号)。若MySQL是5.6或更低版本,utf8mb4可能不支持,报错Unknown character set: 'utf8mb4'。解决方案:升级MySQL到5.7+,或临时将SQL中所有utf8mb4替换为utf8(但会丢失部分字符支持,仅限答辩演示)。
注意:
backup.txt里记录了“2023-10-15 v1.2.0 MySQL5.7适配”,这就是告诉你:这个版本的SQL脚本,只在MySQL5.7+环境下验证通过。别拿5.5去试。
3.2 数据库初始化:两条SQL脚本的执行顺序与依赖关系
auth.sql和exam.sql不能随便执行!顺序错了,exam-service启动会报Table 'exam.exam_paper' doesn't exist:
先执行
auth.sql:
它创建auth_db数据库及sys_user、sys_role等基础表。exam-service启动时,application.yml里spring.datasource.url=jdbc:mysql://localhost:3306/auth_db?...指向此库,用于读取用户角色权限。再执行
exam.sql:
它创建exam_db数据库及exam_paper、exam_question等考试核心表。exam-service的application.yml里spring.datasource.url=jdbc:mysql://localhost:3306/exam_db?...指向此库。
执行命令(以MySQL命令行为例):
# 登录MySQL mysql -u root -p # 执行auth.sql(假设文件在D:\sql\下) source D:/sql/auth.sql; # 执行exam.sql source D:/sql/exam.sql; # 退出 exit;提示:
说明.txt第5行写着“auth.sql必须先于exam.sql执行”,这不是废话,是踩过坑的总结。曾有学生先执行exam.sql,发现exam_db建好了但auth_db没建,auth-service启动失败,折腾两小时才意识到顺序问题。
3.3 一键部署指南:deploy.sh脚本里的“三步启动”真相
doc/部署文档.docx里写的“三步启动”指的是:
第一步:启动config-server
cd config-server mvn spring-boot:run它监听8888端口,提供application.yml配置。auth-service和exam-service的bootstrap.yml里spring.cloud.config.uri=http://localhost:8888指向它。若这步失败,后续所有服务都会报Cannot connect to config server。
第二步:启动auth-service
cd auth-service mvn spring-boot:run它监听8081端口,提供/auth/login、/auth/register接口。启动成功后,访问http://localhost:8081/actuator/health返回{"status":"UP"},证明认证服务就绪。
第三步:启动exam-service
cd exam-service mvn spring-boot:run它监听8082端口,提供/exam/paper/list等接口。启动后,访问http://localhost:8082/actuator/health返回UP,再访问http://localhost:8082/swagger-ui.html(Swagger文档)即可查看所有API。
实操心得:别在IDEA里右键Run,一定要用
mvn spring-boot:run命令!因为pom.xml里<plugin>配置了spring-boot-maven-plugin,它会自动加载bootstrap.yml和application.yml,而IDEA默认只读application.properties。我试过,用IDEA Run,config-server地址永远读不到,死循环报错。
4. 实操过程与核心环节实现:从登录到阅卷,全流程代码级还原
4.1 用户认证流程:JWT令牌生成与刷新的代码级拆解
auth-service的认证核心在AuthController.java:
@PostMapping("/login") public Result login(@RequestBody LoginRequest request) { // 1. 查询用户(SysUserMapper.selectByUsername) SysUser user = userMapper.selectByUsername(request.getUsername()); if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { return Result.fail("用户名或密码错误"); } // 2. 生成JWT Access Token(有效期2小时) String accessToken = jwtUtil.generateToken(user.getUsername(), 2 * 60 * 60); // 3. 生成Refresh Token(有效期7天),存入Redis String refreshToken = UUID.randomUUID().toString(); redisTemplate.opsForValue().set("refresh:" + user.getId(), refreshToken, 7, TimeUnit.DAYS); // 4. 返回令牌 return Result.success(Map.of("accessToken", accessToken, "refreshToken", refreshToken)); }关键点解析:
-jwtUtil.generateToken()调用io.jsonwebtoken.Jwts.builder(),signWith(SignatureAlgorithm.HS512, jwtSecret)使用HS512算法,密钥jwtSecret从config-server的application.yml里获取(spring.cloud.config.name=auth),答辩时可演示修改config-server的yml文件,重启后新密钥立即生效。
- Refresh Token存Redis用"refresh:" + user.getId()作key,避免用户ID冲突;过期时间7, TimeUnit.DAYS硬编码在代码里,说明.txt解释:“7天是平衡安全与用户体验的折中,过短增加用户登录频次,过长增大令牌泄露风险。”
刷新令牌接口/auth/refresh:
@PostMapping("/refresh") public Result refresh(@RequestHeader("Authorization") String authHeader) { String refreshToken = authHeader.substring(7); // Bearer xxxxx // 1. 从Redis查refreshToken是否有效 String storedToken = (String) redisTemplate.opsForValue().get("refresh:" + getCurrentUserId()); if (!Objects.equals(storedToken, refreshToken)) { return Result.fail("Refresh token无效"); } // 2. 生成新accessToken String newAccessToken = jwtUtil.generateToken(getCurrentUsername(), 2 * 60 * 60); return Result.success(Map.of("accessToken", newAccessToken)); }注意:
getCurrentUserId()和getCurrentUsername()从JWT payload里解析,不是查数据库,减少IO。这点在答辩时是加分项——说明你理解了JWT的无状态特性。
4.2 考试组卷逻辑:随机抽题与难度均衡的算法实现
exam-service的组卷核心在PaperService.java:
public ExamPaper generatePaper(Long examId, Integer questionCount) { // 1. 按难度分层抽题:1星题抽30%,2星抽40%,3星抽30% int level1Count = (int) Math.ceil(questionCount * 0.3); int level2Count = (int) Math.ceil(questionCount * 0.4); int level3Count = questionCount - level1Count - level2Count; // 2. 分别查询各难度题目(ExamQuestionMapper.selectByDifficulty) List<ExamQuestion> level1Questions = questionMapper.selectByDifficulty(1, level1Count); List<ExamQuestion> level2Questions = questionMapper.selectByDifficulty(2, level2Count); List<ExamQuestion> level3Questions = questionMapper.selectByDifficulty(3, level3Count); // 3. 合并并打乱顺序(Collections.shuffle) List<ExamQuestion> allQuestions = new ArrayList<>(); allQuestions.addAll(level1Questions); allQuestions.addAll(level2Questions); allQuestions.addAll(level3Questions); Collections.shuffle(allQuestions); // 4. 创建试卷实体 ExamPaper paper = new ExamPaper(); paper.setExamId(examId); paper.setQuestionList(allQuestions); paper.setStatus(1); // 1-已生成 paper.setCreateTime(new Date()); // 5. 保存试卷及题目关联(ExamPaperMapper.insert, ExamPaperQuestionMapper.batchInsert) paperMapper.insert(paper); List<ExamPaperQuestion> paperQuestions = allQuestions.stream() .map(q -> new ExamPaperQuestion(paper.getId(), q.getId(), q.getScore())) .collect(Collectors.toList()); paperQuestionMapper.batchInsert(paperQuestions); return paper; }算法设计意图:
-难度均衡:不是简单随机,而是按比例分层抽样,确保试卷难度分布可控。答辩时可举例:“如果抽10题,1星题3道、2星题4道、3星题3道,避免出现全是难题或全是简单题的情况。”
-性能优化:selectByDifficulty在ExamQuestionMapper.xml里用LIMIT #{count}限制返回条数,避免全表扫描;batchInsert用MyBatis的<foreach>批量插入,比单条插入快5倍以上。
4.3 自动阅卷与成绩计算:客观题判分与主观题留白的工程取舍
exam-service的阅卷逻辑在ExamMarkService.java:
@Transactional public Result markExam(Long paperId, Map<Long, String> userAnswers) { // 1. 查询试卷题目(ExamPaperQuestionMapper.selectByPaperId) List<ExamPaperQuestion> paperQuestions = paperQuestionMapper.selectByPaperId(paperId); // 2. 初始化成绩 BigDecimal totalScore = BigDecimal.ZERO; List<MarkResult> markResults = new ArrayList<>(); // 3. 遍历每道题,对比答案 for (ExamPaperQuestion pq : paperQuestions) { ExamQuestion question = questionMapper.selectById(pq.getQuestionId()); String correctAnswer = question.getCorrectAnswer(); // 正确答案,如"A,B,C" String userAnswer = userAnswers.get(pq.getQuestionId()); // 用户答案 BigDecimal score = BigDecimal.ZERO; MarkResult result = new MarkResult(); result.setQuestionId(pq.getQuestionId()); if (question.getQuestionType() == 1) { // 单选题 score = Objects.equals(correctAnswer, userAnswer) ? new BigDecimal(pq.getScore()) : BigDecimal.ZERO; } else if (question.getQuestionType() == 2) { // 多选题 // 答案完全一致才给分(不判部分分) score = Objects.equals(correctAnswer, userAnswer) ? new BigDecimal(pq.getScore()) : BigDecimal.ZERO; } else if (question.getQuestionType() == 3) { // 判断题 score = Objects.equals(correctAnswer, userAnswer) ? new BigDecimal(pq.getScore()) : BigDecimal.ZERO; } else { // 主观题,不自动判分,留空 score = BigDecimal.ZERO; result.setIsSubjective(true); } result.setUserAnswer(userAnswer); result.setCorrectAnswer(correctAnswer); result.setScore(score); markResults.add(result); totalScore = totalScore.add(score); } // 4. 保存成绩(ExamMarkMapper.insert) ExamMark mark = new ExamMark(); mark.setPaperId(paperId); mark.setTotalScore(totalScore); mark.setMarkTime(new Date()); markMapper.insert(mark); // 5. 保存每题判分详情(ExamMarkDetailMapper.batchInsert) List<ExamMarkDetail> details = markResults.stream() .map(r -> new ExamMarkDetail(mark.getId(), r.getQuestionId(), r.getUserAnswer(), r.getCorrectAnswer(), r.getScore(), r.isSubjective())) .collect(Collectors.toList()); detailMapper.batchInsert(details); return Result.success(Map.of("totalScore", totalScore, "details", markResults)); }工程取舍说明:
-多选题不判部分分:代码里Objects.equals(correctAnswer, userAnswer)要求答案字符串完全一致(如正确答案是"A,C",用户答"A"不得分)。说明.txt解释:“教育考试中多选题通常全对才得分,避免主观评分争议。”
-主观题明确留空:question.getQuestionType() == 4时,score = BigDecimal.ZERO且result.setIsSubjective(true),前端展示“待人工批阅”。这体现了系统边界意识——不强行AI判分,把专业判断留给教师。
5. 常见问题与排查技巧实录:答辩现场救急指南
5.1 启动失败类问题速查表
| 现象 | 可能原因 | 排查命令/位置 | 解决方案 |
|---|---|---|---|
auth-service启动报Caused by: java.net.ConnectException: Connection refused: connect | config-server未启动或端口不对 | 检查config-server是否运行;netstat -ano \| findstr :8888(Windows)或lsof -i :8888(Mac/Linux) | 先启动config-server,确认8888端口被占用 |
exam-service启动报java.sql.SQLException: Access denied for user 'root'@'localhost' | MySQL用户名密码错误 | 查看config-server的application.yml里spring.datasource.username和password | 修改config-server/src/main/resources/application.yml,重启config-server |
mvn spring-boot:run报Could not resolve dependencies for project ... | Maven仓库缺少依赖(如spring-cloud-starter-config) | mvn dependency:tree \| grep cloud检查依赖树 | 删除~/.m2/repository/org/springframework/cloud/目录,重新mvn clean compile |
访问http://localhost:8082/swagger-ui.html显示404 | Swagger配置未生效 | 检查exam-service/pom.xml是否有springfox-swagger2和springfox-swagger-ui依赖;ExamApplication.java是否有@EnableSwagger2 | 确认依赖版本匹配(springfox-swagger2:2.9.2对应SpringBoot2.x),添加@EnableSwagger2注解 |
5.2 功能异常类问题排查
问题:登录后无法访问/exam/paper/list,返回403 Forbidden
-原因:auth-service签发的JWT未传入exam-service,或令牌过期。
-排查:
1. 浏览器F12打开Network,登录后看/auth/login响应头Authorization: Bearer xxxxx是否返回;
2. 调用/exam/paper/list时,请求头是否携带Authorization: Bearer xxxxx;
3.auth-service的jwtUtil.generateToken()里2 * 60 * 60是7200秒(2小时),检查系统时间是否准确(时间偏差大会导致JWT解析失败)。
-解决:前端调用API时,从登录响应中提取accessToken,存入localStorage,后续请求统一添加Authorization头。
问题:组卷后试卷题目重复
-原因:questionMapper.selectByDifficulty()未加ORDER BY RAND()或分页参数错误,导致多次查询返回相同题目。
-排查:
1. 直接执行SQL:SELECT * FROM exam_question WHERE difficulty_level = 1 ORDER BY RAND() LIMIT 3;看结果是否随机;
2. 检查ExamQuestionMapper.xml里<select id="selectByDifficulty">的SQL是否包含ORDER BY RAND()。
-解决:在Mapper XML的SQL末尾添加ORDER BY RAND(),如SELECT * FROM exam_question WHERE difficulty_level = #{level} ORDER BY RAND() LIMIT #{count}。
5.3 答辩现场救急三招
“老师,这个功能我还没做完” → 改成“这个模块我预留了扩展接口”
例如,monitor-service目前只暴露Actuator端点,若被问“有没有告警功能?”,不要说“没做”,而是打开monitor-service/src/main/java/com/example/monitor/config/MonitorConfig.java,指着@Bean @ConditionalOnProperty(name = "monitor.alert.enabled", havingValue = "true")说:“我预留了告警开关,配置monitor.alert.enabled=true即可启用邮件告警,具体实现是调用JavaMailSender,代码框架已搭好,只需补充SMTP配置。”“这段代码我看不懂” → 指向文档和注释
若导师指着一段MyBatis动态SQL问作用,立刻打开exam-service/src/main/resources/mapper/ExamQuestionMapper.xml,找到对应<select>标签,读出注释:“// 根据难度和数量随机抽取题目,用于组卷”。再打开说明.txt,指出第8行:“动态SQL使用<if>和<bind>实现条件拼接,避免SQL注入。”“数据库压力大怎么办?” → 展示已做的优化
打开exam-service/src/main/resources/application.yml,找到spring.datasource.hikari配置段:yaml maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000
解释:“HikariCP连接池最大20个连接,空闲超10分钟释放,连接最长30分钟,避免长连接占资源。exam.sql里exam_paper表的exam_id字段已建索引(ALTER TABLE exam_paper ADD INDEX idx_exam_id (exam_id);),查询试卷题目时走索引,QPS可达200+。”
最后分享一个小技巧:答辩前,把
backup.txt里最新版本号(如v1.2.0)和README.md第一行标题复制到PPT封面页。导师看到“v1.2.0”就知道你有版本管理意识,比说一百遍“我做了很多工作”都管用。
本文还有配套的精品资源,点击获取
简介:直接拿来就能跑的Java在线考试系统,专为计算机专业毕业设计准备。用SpringBoot搭建,前后端代码齐全,数据库用MySQL,附带auth.sql和exam.sql两个建库脚本,开箱即用。部署文档.docx写得特别细,从环境配置、依赖安装到启动命令全都有,本地调试三步就能搞定。系统拆成auth-service(登录注册权限)、exam相关模块(出题、组卷、考试、阅卷)、monitor-service(服务状态监控)和config-server(统一配置管理),结构清晰,方便答辩时讲清楚模块分工。资源包里还有说明.txt快速上手提示、backup.txt记录版本变更、pom.xml多份确保Maven构建稳定,前端资源放在app/和fileServer/目录下,测试配置karma.conf.js也一并提供。所有代码已在标准JDK8+、Maven3.6+、MySQL5.7环境下验证通过,不改代码也能演示完整流程,适合课程实践、中期检查或最终答辩。
本文还有配套的精品资源,点击获取