基于 Spring Boot + Vue3 的教师资格证备考学习平台设计与实现
温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片!
一、项目背景与意义
教师资格证考试报考人数逐年增长,考生群体以在校师范生、非师范转岗人员和在职备考者为主。传统备考方式依赖纸质教材、线下培训班和零散的网盘资料,普遍存在三个问题:一是学习内容分散,知识点、真题、错题难以形成闭环;二是缺乏学习进度跟踪,考生很难判断自己处于什么水平;三是缺少针对性练习,刷题后无法定位薄弱章节。
本项目基于 Spring Boot + Vue3 构建一个教师资格证备考学习平台,把课程学习、章节练习、真题模拟、错题本、学习计划与数据统计整合到同一套系统中,帮助考生形成“学—练—测—复盘”的完整备考链路,同时也可作为高校计算机专业毕业设计或课程设计的技术实践项目。
二、系统功能需求分析
2.1 用户角色划分
- 考生用户:浏览课程、观看视频、做章节练习与模拟考试、查看错题本与学习报告。
- 教师/内容管理员:维护课程章节、上传视频与讲义、录入题库、组卷与发布考试。
- 系统管理员:管理用户与角色权限、维护考试科目与知识点分类、查看平台运营数据。
2.2 核心功能模块
| 模块 | 主要功能 |
|---|---|
| 用户中心 | 注册登录、个人信息、报考学段选择(幼儿园/小学/中学)、学习目标设置 |
| 课程学习 | 科目分类、章节列表、视频播放、讲义下载、学习进度记录 |
| 题库练习 | 章节练习、随机练习、真题模拟、答案解析、收藏题目 |
| 考试测评 | 限时组卷、自动交卷、客观题自动判分、成绩单与排名 |
| 错题本 | 错题自动归集、按知识点分类、重做与掌握度标记 |
| 学习统计 | 学习时长、做题量、正确率趋势、薄弱知识点分析 |
| 后台管理 | 课程管理、题库管理、试卷管理、用户管理、数据看板 |
三、系统架构设计
3.1 总体架构
系统采用前后端分离架构。前端使用 Vue3 + Vite 构建单页应用,后端使用 Spring Boot 提供 RESTful 接口,数据层使用 MySQL 存储业务数据,Redis 缓存登录令牌与热点题目,文件资源(视频、讲义、头像)存放在对象存储或本地文件服务中。
flowchart TD A[Vue3 前端应用] -->|HTTP/JSON| B[Nginx 反向代理] B --> C[Spring Boot 应用] C --> D[Service 业务层] D --> E[MyBatis-Plus 数据访问] E --> F[(MySQL)] D --> G[(Redis 缓存)] D --> H[对象存储/文件服务] C --> I[JWT 认证拦截器]3.2 技术选型
| 层次 | 技术 | 说明 |
|---|---|---|
| 前端框架 | Vue3 + Vite | 组合式 API,构建速度快 |
| UI 组件库 | Element Plus | 后台管理与表单页面快速搭建 |
| 状态管理 | Pinia | 管理用户信息、学习进度等全局状态 |
| 路由 | Vue Router | 路由守卫控制登录与角色权限 |
| 网络请求 | Axios | 统一封装请求拦截与错误处理 |
| 后端框架 | Spring Boot 3.x | 自动装配、内嵌 Tomcat |
| 持久层 | MyBatis-Plus | 简化 CRUD 与分页查询 |
| 数据库 | MySQL 8.0 | 业务数据存储 |
| 缓存 | Redis | Token、验证码、热点数据缓存 |
| 认证 | Spring Security + JWT | 无状态登录与接口鉴权 |
四、数据库设计
围绕“用户—课程—题库—考试—记录”五条主线设计核心表,主要表结构如下:
| 表名 | 说明 | 关键字段 |
|---|---|---|
| sys_user | 用户表 | id、username、password、role、exam_level、create_time |
| course | 课程表 | id、title、subject_id、cover、description |
| course_chapter | 课程章节表 | id、course_id、title、video_url、duration、sort |
| question | 题目表 | id、subject_id、knowledge_id、type、content、options、answer、analysis |
| exam_paper | 试卷表 | id、title、total_score、duration、status |
| exam_record | 考试记录表 | id、user_id、paper_id、score、start_time、submit_time |
| answer_record | 答题明细表 | id、record_id、question_id、user_answer、is_correct |
| study_progress | 学习进度表 | id、user_id、chapter_id、progress、last_time |
| wrong_book | 错题本表 | id、user_id、question_id、wrong_count、mastered |
题目表通过type字段区分单选、多选、判断和简答,选项统一以 JSON 字符串存储,便于扩展不同题型;答题明细表与考试记录表通过record_id关联,支撑成绩单与错题归集。
五、后端核心实现
5.1 项目结构
teacher-exam-server ├── src/main/java/com/example/exam │ ├── common # 统一响应、异常处理、常量 │ ├── config # Security、Redis、MyBatis-Plus 配置 │ ├── controller # 接口层 │ ├── service # 业务层 │ ├── mapper # 数据访问层 │ ├── entity # 实体类 │ └── dto # 请求/响应对象 └── src/main/resources ├── application.yml └── mapper # XML 映射文件5.2 统一响应与全局异常处理
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMessage("success"); r.setData(data); return r; } public static <T> Result<T> fail(String message) { Result<T> r = new Result<>(); r.setCode(500); r.setMessage(message); return r; } }@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result<Void> handleBusiness(BusinessException e) { return Result.fail(e.getMessage()); } @ExceptionHandler(Exception.class) public Result<Void> handleException(Exception e) { log.error("系统异常", e); return Result.fail("系统繁忙,请稍后重试"); } }5.3 JWT 登录认证
@Service @RequiredArgsConstructor public class AuthService { private final UserMapper userMapper; private final JwtUtil jwtUtil; public LoginVO login(LoginDTO dto) { SysUser user = userMapper.selectOne( new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, dto.getUsername())); if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { throw new BusinessException("用户名或密码错误"); } String token = jwtUtil.generateToken(user.getId(), user.getRole()); LoginVO vo = new LoginVO(); vo.setToken(token); vo.setUsername(user.getUsername()); vo.setRole(user.getRole()); return vo; } }5.4 考试交卷与自动判分
交卷时先校验考试状态,再逐题比对答案,客观题自动判分,主观题标记为待批阅,最后汇总成绩并写入错题本。
@Transactional(rollbackFor = Exception.class) public ExamResultVO submit(Long recordId, List<AnswerDTO> answers) { ExamRecord record = examRecordMapper.selectById(recordId); if (record == null || record.getSubmitTime() != null) { throw new BusinessException("考试记录不存在或已交卷"); } int correctCount = 0; int totalScore = 0; for (AnswerDTO dto : answers) { Question question = questionMapper.selectById(dto.getQuestionId()); boolean correct = question.getAnswer().equalsIgnoreCase(dto.getUserAnswer()); if (correct) { correctCount++; totalScore += question.getScore(); } else { wrongBookService.saveWrong(record.getUserId(), question.getId()); } AnswerRecord ar = new AnswerRecord(); ar.setRecordId(recordId); ar.setQuestionId(question.getId()); ar.setUserAnswer(dto.getUserAnswer()); ar.setIsCorrect(correct); answerRecordMapper.insert(ar); } record.setScore(totalScore); record.setSubmitTime(LocalDateTime.now()); examRecordMapper.updateById(record); ExamResultVO vo = new ExamResultVO(); vo.setScore(totalScore); vo.setCorrectCount(correctCount); vo.setTotalCount(answers.size()); return vo; }六、前端核心实现
6.1 请求封装与路由守卫
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( res => { const { code, message, data } = res.data if (code === 200) return data ElMessage.error(message || '请求失败') return Promise.reject(new Error(message)) }, err => { if (err.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(err) } ) export default request6.2 在线答题页面
答题页使用 Pinia 保存当前试卷与作答状态,支持题目切换、答题卡跳转和倒计时自动交卷。
<template> <div class="exam-page"> <div class="question-area"> <h3>{{ currentIndex + 1 }}. {{ currentQuestion.content }}</h3> <el-radio-group v-model="answers[currentQuestion.id]"> <el-radio v-for="(item, key) in currentQuestion.options" :key="key" :label="key" >{{ key }}. {{ item }}</el-radio> </el-radio-group> </div> <div class="answer-card"> <el-button v-for="(q, i) in questions" :key="q.id" :type="answers[q.id] ? 'primary' : 'default'" @click="currentIndex = i" >{{ i + 1 }}</el-button> </div> </div> </template> <script setup> import { ref, computed, onMounted } from 'vue' import { useExamStore } from '@/stores/exam' const examStore = useExamStore() const questions = ref([]) const currentIndex = ref(0) const answers = ref({}) const currentQuestion = computed(() => questions.value[currentIndex.value]) onMounted(async () => { questions.value = await examStore.loadPaper() }) </script>七、关键难点与解决方案
- 考试防作弊:前端记录切屏次数并上报,后端在交卷时校验考试时长与切屏记录,超限标记异常。
- 自动交卷一致性:使用 Redis 存储考试剩余时间,交卷接口做幂等校验,避免重复提交产生多条记录。
- 错题归集性能:错题本按用户与题目建立唯一索引,采用“存在则更新次数、不存在则插入”的方式,避免重复数据。
- 学习进度统计:章节进度按视频播放百分比异步上报,前端节流,后端合并更新,减少写库压力。
- 题库扩展性:题型通过枚举与策略模式处理判分逻辑,新增题型只需实现对应判分策略。
八、系统测试与部署
测试阶段重点覆盖登录鉴权、组卷逻辑、交卷判分和错题归集四类场景,使用 Postman 与 JUnit 分别完成接口测试和单元测试。部署时前端执行npm run build生成静态资源,交由 Nginx 托管并反向代理/api到后端;后端打包为 jar,通过java -jar或 Docker 运行,MySQL 与 Redis 使用容器编排统一管理。
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/teacher_exam?useUnicode=true&characterEncoding=utf8 username: root password: 123456 data: redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true九、总结与展望
本文围绕教师资格证备考场景,完成了基于 Spring Boot + Vue3 的学习平台的需求分析、架构设计、数据库设计与核心功能实现。系统通过课程学习、题库练习、模拟考试与错题本的联动,帮助考生建立完整的备考闭环。
后续可从三个方向继续完善:一是引入推荐算法,根据错题分布推荐薄弱知识点课程;二是增加移动端适配或小程序端,方便碎片化学习;三是接入大模型能力,提供主观题智能批阅与个性化学习建议。