每年到 Java 毕设选题的季节,总有一批人被同一个问题卡住:选太简单的系统,怕评委一句“你的难点在哪里”直接终结答辩;选太复杂的系统,又怕开发周期失控,最后连一个能演示的版本都拿不出来。这不是个别现象,而是 Java 方向毕业设计里最普遍的焦虑。如果你正在“图书管理系统”和“分布式秒杀系统”这两个极端之间来回试探,那么像“基于 SpringBoot 的驾校报名与考勤系统”这种处在中间位置的项目,反而更值得认真考虑。
从标题看,这是一个标准的 SpringBoot + Vue 前后端分离项目,业务上覆盖了“报名”和“考勤”两条主线。表面看它仍然是业务管理系统,和传统 CRUD 系统没有代差,但往下拆一层就会发现:用户角色、报名流程、状态流转、课程排期、考勤打卡、学时统计,这些功能组合起来,已经足够撑起一份结构完整的毕业设计论文,也足够让答辩现场有话可讲。这个选题的价值不在于业务创新,而在于它用一套大多数学生能驾驭的技术栈,把毕业设计评分标准里的技术覆盖面串了起来。
这篇文章会从选题思路、系统定位、技术选型、数据库设计、前后端核心代码、本地运行流程、常见问题排查,再到答辩准备,把这个项目完整拆开讲。无论你是还没定题,还是已经下载了类似源码但不知道怎么讲清楚,建议收藏后仔细看一遍。
1. 这篇文章真正要解决的问题
很多学生做毕设最大的障碍,不是不会写代码,而是“不知道自己下载的项目是怎么跑起来的”。CSDN 上类似的驾校管理系统源码非常多,但真正能帮到你的不是那堆能运行的代码,而是代码背后的逻辑链条。
这篇文章尝试回答三个具体问题。
第一个问题:为什么“驾校报名与考勤”适合做毕设选题?它的业务复杂度和技术覆盖面处于一个什么样的位置?搞清楚这一点,你才能在选题理由和论文背景部分写出有说服力的内容,而不是只写一句“本项目实现了驾校的信息化管理”。
第二个问题:前后端分离的项目在本地怎么完整跑通?很多同学的后端接口明明能访问,前端页面就是一排报错;前端页面渲染出来了,接口又连不上数据库。跨域、端口、请求地址、依赖版本、数据库账号密码,每一个环节都可能中断你的演示流程。这篇文章会给出一个可以直接照着操作的运行路径。
第三个问题:答辩时怎样讲,才能避免被评委问倒?毕设评分从来不只是看功能,还要看你有没有真正理解系统设计。为什么数据库要分成这么多张表?为什么登录要用 Token 而不是 Session?考勤数据怎么统计才合理?这些问题如果只停留在“会敲代码”的层面,很容易被追问到沉默。
一句话总结:这篇博客要帮你看清这个项目的业务边界和技术边界,让你既能把它跑起来,也能把它讲明白。
2. 系统定位:驾校报名与考勤系统到底在解决什么业务问题
理解一个系统,首先从业务痛点开始。传统驾校的线下操作流程通常是:学员到前台咨询,填写纸质报名表,缴纳费用,管理员手动分配教练,每次培训由教练在纸质表格上记录学员出勤,培训结束后人工统计是否满足学时要求。
这个流程最明显的三个痛点是:
- 报名信息分散,管理员难以快速检索学员状态。
- 考勤完全依赖人工,容易出现漏记、补记、代签。
- 学时统计工作量大,学员和教练之间容易产生争议。
驾校报名与考勤系统要解决的就是这三个问题。它的业务模型可以拆成两条主线。
第一条是报名管理主线。学员注册账号后可以提交报名申请,选择培训类型,比如 C1、C2,系统记录学员的身份信息、联系方式、报名时间、缴费状态。管理员在后台审核报名信息,确认后分配教练。报名状态通常包含待审核、已通过、已拒绝、已缴费、已完成等状态。
第二条是考勤管理主线。教练创建培训课程,学员在课程时间段内进行签到和签退,系统记录每一次考勤时间和出勤状态。通过考勤记录,可以自动汇总每个学员的总学时。这个功能对驾校管理非常重要,也是整个系统相对普通 CRUD 更进一步的地方。
从用户角色来看,这个系统通常包含三类角色。
管理员负责用户管理、课程管理、报名审核、数据统计,是整个系统的核心运营角色。
教练负责查看自己的课程安排、录入课程信息、确认学员出勤,有的版本还会让教练记录学员培训进度。
学员负责注册登录、提交报名、查看课程、进行考勤打卡、查看自己的学时记录。
这里真正值得注意的一点是:虽然系统功能听起来不复杂,但它在数据库层面已经把“用户”和“学员/教练”拆开,在业务层面又设计了报名状态流转和考勤汇总逻辑。这正是毕业设计论文可以深入展开的两个方向。
3. 技术选型:为什么 SpringBoot + Vue 前后端分离成为当前毕设主流
如果你翻看近几年 Java 方向的毕业设计,会发现“SpringBoot + Vue + MySQL”几乎成了默认组合。这并非偶然,而是技术栈演进和实际开发需求共同作用的结果。
在 JSP 时代,一个后端开发人员既要写 Java 业务逻辑,又要写 HTML 页面,还要在 JSP 里混入大量标签和脚本。前后端耦合严重,页面改动非常痛苦,现在的企业项目基本已经不再采用这种模式。
前后端分离架构的核心变化在于:后端只负责提供 JSON 格式的接口数据,不再关心页面如何渲染;前端通过 HTTP 请求调用后端接口,独立负责页面的交互和展示。开发时两个团队或两种技术可以并行,部署时也可以分开部署。
对于毕业设计来说,前后端分离还有三个非常实际的收益。
第一,演示效果好。答辩时你可以先打开 Swagger 或 Knife4j 接口文档页面,展示系统一共有多少个接口、每个接口接收什么参数,再打开 Vue 前端页面展示界面效果。这种“接口 + 页面”的双层展示方式,比传统的单体 JSP 页面更直观。
第二,回答问题有素材。评委大概率会问“什么是前后端分离”“为什么这样设计”,你至少能从职责划分、并行开发、部署方式等角度展开回答。
第三,贴近就业需求。企业招聘 Java 开发,越来越要求候选人对前后端分离有真实认知。即使你以后做纯后端,也要理解前端是怎么调用你的接口的。
从具体技术组件来看,一个典型的驾校报名与考勤系统通常会涉及以下内容,这里不写死版本号,以你实际下载的项目为准:
| 层次 | 技术组件 | 作用 |
|---|---|---|
| 后端框架 | SpringBoot | 快速搭建独立运行的 Spring 应用 |
| Web 层 | Spring MVC | 处理 HTTP 请求与响应 |
| 数据持久层 | MyBatis 或 MyBatis-Plus | 简化数据库操作 |
| 数据库 | MySQL | 存储业务数据 |
| 认证方案 | JWT 或 Spring Security | 登录认证与接口权限控制 |
| 接口文档 | Swagger 或 Knife4j | 生成可调试的接口文档 |
| 前端框架 | Vue 2 或 Vue 3 | 构建页面 |
| 前端路由 | Vue Router | 页面路由跳转 |
| 状态管理 | Vuex 或 Pinia | 管理登录状态和全局数据 |
| HTTP 请求 | Axios | 前端调用后端接口 |
| UI 组件 | Element UI 或 Element Plus | 快速搭建后台管理页面 |
这套技术栈的另一层价值在于:它本身就是 Java 后端岗位面试中的基础要求。做毕设的过程,相当于一次完整的项目复习。
4. 数据库设计思路与核心表结构
数据库设计是毕业设计论文中非常容易拿分、也非常容易扣分的部分。很多学生喜欢把所有字段塞进一张大表,或者直接复制一套开源项目的表结构,连字段注释都没改。这种做法在答辩时很容易被追问。
驾校报名与考勤系统的数据库设计,需要围绕“用户”“报名”“考勤”三个核心概念展开。
先设计基础的用户表。由于系统包含管理员、教练、学员三种角色,通常的做法不是创建三张完全独立的表,而是创建一张统一的用户表,通过 role 字段区分角色。这样做的好处是登录认证时只需要查一张表,后续扩展新角色也不用改表结构。
再考虑学员与教练的特殊信息。学员需要身份证号、培训车型、报名状态等字段;教练需要驾照类型、教龄、可带车型等字段。这部分可以用扩展表解决:sys_user 表保存账号和基础信息,student 表和 coach 表保存角色扩展信息,通过 user_id 字段关联。
报名业务单独建表。报名记录表不能直接写在学员表里,因为一个学员可能有多次报名记录,报名状态也需要独立跟踪。报名记录关联学员 ID、培训车型、报名时间、缴费金额、缴费状态等字段。
考勤业务单独建表。考勤记录关联报名记录或课程记录,记录打卡时间和状态。这样设计的好处是:统计某个学员的学时,只需要按学员维度汇总考勤表;统计某次课程的出勤情况,只需要按课程维度查考勤表。
下面是核心表的一个设计参考。注意:表名不要使用 user 这类 MySQL 保留字,推荐使用 sys_user。
| 表名 | 作用 | 核心字段 |
|---|---|---|
| sys_user | 用户账号表 | id, username, password, role, real_name, phone |
| student | 学员扩展信息表 | id, user_id, id_card, apply_type, status |
| coach | 教练扩展信息表 | id, user_id, car_type, years |
| course | 培训课程表 | id, coach_id, course_date, start_time, end_time, max_count |
| enrollment | 报名记录表 | id, student_id, course_type, amount, pay_status |
| attendance | 考勤记录表 | id, enrollment_id, course_id, check_in_time, check_out_time, status |
来看三个核心表的建表 SQL。第一个是用户表:
CREATE TABLE `sys_user` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` VARCHAR(50) NOT NULL COMMENT '登录用户名', `password` VARCHAR(100) NOT NULL COMMENT '登录密码,须加密存储', `role` VARCHAR(20) NOT NULL COMMENT '角色:admin/coach/student', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';第二个是报名记录表:
CREATE TABLE `enrollment` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `student_id` BIGINT NOT NULL COMMENT '学员ID,关联student表', `course_type` VARCHAR(20) NOT NULL COMMENT '培训车型,如C1/C2', `amount` DECIMAL(10,2) DEFAULT NULL COMMENT '报名费用', `pay_status` VARCHAR(20) DEFAULT 'UNPAID' COMMENT '缴费状态:UNPAID/PAID/REFUND', `status` VARCHAR(20) DEFAULT 'PENDING' COMMENT '报名状态:PENDING/APPROVED/REJECTED', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报名记录表';第三个是考勤记录表:
CREATE TABLE `attendance` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `enrollment_id` BIGINT NOT NULL COMMENT '报名记录ID', `course_id` BIGINT NOT NULL COMMENT '课程ID', `check_in_time` DATETIME DEFAULT NULL COMMENT '签到时间', `check_out_time` DATETIME DEFAULT NULL COMMENT '签退时间', `status` VARCHAR(20) DEFAULT 'NORMAL' COMMENT '考勤状态:NORMAL/LATE/ABSENT', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤记录表';注意几个设计细节。密码字段长度不要设置成 20,因为经过 BCrypt 加密后的字符串长度远超过明文密码,一般建议 100。金额字段建议使用 DECIMAL 而不是 DOUBLE,避免浮点精度问题。所有表都应该包含 create_time 字段,这对论文中的系统维护和日志分析是加分项。
5. 后端核心代码实现:从配置文件到业务接口
后端代码是整个系统的核心,也是论文技术部分最容易展开的内容。下面按照项目结构、基础配置、业务接口三个层次来说明。
5.1 后端项目结构
一个典型的 SpringBoot 后端项目目录结构如下:
src/main/java/com/example/driving/ ├── DrivingApplication.java // 启动类 ├── config/ │ ├── CorsConfig.java // 跨域配置 │ └── WebMvcConfig.java // Web 配置 ├── controller/ // 控制器层 │ ├── AuthController.java │ ├── EnrollmentController.java │ └── AttendanceController.java ├── service/ // 业务逻辑层 │ ├── EnrollmentService.java │ └── AttendanceService.java ├── mapper/ // MyBatis 数据访问层 │ ├── EnrollmentMapper.java │ └── AttendanceMapper.java ├── entity/ // 实体类 │ ├── SysUser.java │ └── Enrollment.java ├── common/ // 通用返回结果和异常 │ ├── Result.java │ └── GlobalExceptionHandler.java └── util/ ├── JwtUtil.java // JWT 工具类 └── DateUtil.java这种分层结构非常标准,controller 负责接收参数和返回结果,service 负责业务逻辑,mapper 负责数据库操作。答辩时如果评委问你“三层架构为什么这样分层”,你可以从职责单一、可测试性、可维护性三个角度回答。
5.2 application.yml 基础配置
后端配置文件是项目运行的第一个关键点,最常见的错误都集中在数据库连接和端口配置上。
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/driving_school?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.driving.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置说明:
- server.port 是后端接口的启动端口,前端请求时会用到。
- url 中的数据库名必须和本地创建的库保持一致。
- map-underscore-to-camel-case 开启后,数据库的 create_time 字段可以自动映射到 Java 实体的 createTime 属性。
- log-impl 配置开发阶段可以看到每次执行的 SQL 日志,排查问题时非常有用。
如果你的项目用的是 MyBatis 而不是 MyBatis-Plus,对应的配置略有不同,但核心思路一致。配置完成后,务必先在数据库客户端测试一下账号密码是否可用,这是后端启动失败最常见的原因。
5.3 报名业务接口实现
以一个报名业务接口为例。前端提交一个报名请求时,后端经历的过程是:接收参数,校验学员是否存在,检查是否已经报名,创建报名记录,返回结果。
Controller 层:
// 文件路径:src/main/java/com/example/driving/controller/EnrollmentController.java @RestController @RequestMapping("/api/enrollment") public class EnrollmentController { @Resource private EnrollmentService enrollmentService; @PostMapping("/submit") public Result submit(@RequestBody EnrollmentDTO dto) { enrollmentService.submitEnrollment(dto); return Result.success("报名申请提交成功"); } @GetMapping("/my") public Result myEnrollments(@RequestParam Long studentId) { return Result.success(enrollmentService.getMyEnrollments(studentId)); } }Service 层要处理事务。报名不是简单的 insert,它涉及报名记录创建和考勤记录初始化两个动作,任何一个失败都需要整体回滚。
// 文件路径:src/main/java/com/example/driving/service/EnrollmentService.java @Service public class EnrollmentService { @Resource private EnrollmentMapper enrollmentMapper; @Resource private AttendanceMapper attendanceMapper; @Transactional(rollbackFor = Exception.class) public void submitEnrollment(EnrollmentDTO dto) { // 1. 校验学员是否存在 if (!studentExists(dto.getStudentId())) { throw new BusinessException("学员信息不存在"); } // 2. 校验是否重复报名 int count = enrollmentMapper.countByStudentId(dto.getStudentId()); if (count > 0) { throw new BusinessException("该学员已存在报名记录"); } // 3. 创建报名记录 Enrollment enrollment = new Enrollment(); enrollment.setStudentId(dto.getStudentId()); enrollment.setCourseType(dto.getCourseType()); enrollment.setAmount(dto.getAmount()); enrollment.setPayStatus("UNPAID"); enrollment.setStatus("PENDING"); enrollmentMapper.insert(enrollment); // 4. 初始化考勤信息,这里可以做逻辑校验 attendanceMapper.initByEnrollmentId(enrollment.getId()); } }这里就有一个非常好的答辩提问点:为什么 @Transactional 注解能保证事务?你可以回答,它通过 Spring 的 AOP 机制,在方法执行前开启事务,方法正常结束后提交事务,抛出异常后回滚事务。默认情况下,只有 RuntimeException 才触发回滚,所以通常设置 rollbackFor = Exception.class 覆盖所有异常。
5.4 考勤签到接口实现
考勤打卡业务的一个重要点是防止重复打卡。学员签到后再次签到,系统应该提示“您已签到”,而不是生成第二条记录。这里通常在数据库层面用唯一约束或代码层面做判断。
// 文件路径:src/main/java/com/example/driving/service/AttendanceService.java @Service public class AttendanceService { @Resource private AttendanceMapper attendanceMapper; public void checkIn(Long enrollmentId, Long courseId) { // 校验该学员本次课程是否已签到 Attendance attendance = attendanceMapper.selectByEnrollmentAndCourse(enrollmentId, courseId); if (attendance != null && attendance.getCheckInTime() != null) { throw new BusinessException("请勿重复签到"); } attendanceMapper.updateCheckInTime(enrollmentId, courseId, new Date()); } }这个接口的难点不在语法,而在于并发场景。如果学员在极短的时间内连续点击两次签到按钮,代码层面就可能同时通过校验。生产环境通常会结合数据库唯一索引或者 Redis 分布式锁来解决。作为毕设,你只需要在代码里做一层校验,并在论文里说明这个设计的不足与改进方向,就已经比大多数同学思考得更深入了。
6. 前端页面与接口对接
前端部分的核心是页面组件开发和接口对接。下面从项目结构、请求封装、核心页面逻辑三个角度说明。
6.1 前端项目结构
一个典型的 Vue 前端项目目录如下:
src/ ├── api/ │ ├── request.js // axios 实例封装 │ ├── enrollment.js // 报名相关接口 │ └── attendance.js // 考勤相关接口 ├── router/ │ └── index.js // 路由配置 ├── store/ │ └── user.js // 用户登录状态 ├── views/ │ ├── login/index.vue // 登录页 │ ├── enrollment/index.vue // 报名管理页 │ └── attendance/index.vue // 考勤管理页 ├── App.vue └── main.js这个结构需要和上面的后端接口对应起来。api 目录下的每个文件对应一类业务接口,views 目录下的每个文件夹对应一个页面功能。
6.2 封装 axios 请求实例
前端调用后端接口时,最常遇到的问题有两个:跨域报错和请求头缺少 Token。通过封装 request.js 可以统一处理。
// 文件路径:src/api/request.js import axios from 'axios' const request = axios.create({ baseURL: 'http://localhost:8080/api', timeout: 10000 }) // 请求拦截器:自动携带 token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) // 响应拦截器:统一处理返回结果和登录失效 request.interceptors.response.use( response => { return response.data }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export default request这段代码的关键在于请求拦截器和响应拦截器。请求时自动携带 Token,响应时统一处理 401 状态并跳转登录页。这是实际项目中非常标准的写法,也是答辩时可以展示的亮点。
6.3 报名管理页面核心逻辑
报名管理页面需要区分学员端和管理员端。学员端展示自己的报名记录,管理员端展示所有学员的报名记录并能审核。
一个典型的报名接口调用示例:
// 文件路径:src/api/enrollment.js import request from './request' // 提交报名 export function submitEnrollment(data) { return request({ url: '/enrollment/submit', method: 'post', data: data }) } // 查询我的报名记录 export function getMyEnrollments(studentId) { return request({ url: '/enrollment/my', method: 'get', params: { studentId } }) } // 管理员审核报名 export function reviewEnrollment(id, status) { return request({ url: '/enrollment/review', method: 'put', params: { id, status } }) }在 Vue 页面中调用:
<template> <div class="enrollment-page"> <el-form :model="form" label-width="100px"> <el-form-item label="培训车型"> <el-select v-model="form.courseType"> <el-option label="C1 手动挡" value="C1" /> <el-option label="C2 自动挡" value="C2" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSubmit">提交报名</el-button> </el-form-item> </el-form> </div> </template> <script> import { submitEnrollment } from '@/api/enrollment' export default { data() { return { form: { studentId: '', courseType: 'C1', amount: 0 } } }, methods: { handleSubmit() { submitEnrollment(this.form).then(res => { this.$message.success(res.message || '报名成功') }).catch(err => { this.$message.error(err.message || '报名失败') }) } } } </script>这里要注意的是,Element UI 和 Element Plus 的用法不完全一样,具体以你下载的项目使用的版本为准。如果只是参考这个思路,核心的调用流程是一致的。
前端与后端对接是最容易出问题的一环,最常见的报错是跨域请求被拦截。解决跨域有两种方式:前端开发环境配置代理,或者后端配置跨域过滤器。
7. 本地运行全流程:环境准备、启动与效果验证
拿到项目源码后,很多人习惯直接运行,结果报错后不知道怎么排查。正确的做法是分阶段执行,每完成一步就验证一步。
7.1 环境准备清单
| 环境项 | 说明 | 验证命令 |
|---|---|---|
| JDK | 需要 8 及以上版本,版本与项目要求匹配 | java -version |
| Maven | 用于后端依赖管理和编译 | mvn -v |
| Node.js | 用于前端依赖安装和项目启动 | node -v |
| npm 或 yarn | 前端包管理器 | npm -v |
| MySQL | 数据库,建议 5.7 及以上 | mysql --version |
| IDE | IDEA 或 VSCode,按自己习惯 | — |
版本不要盲目追求最新,关键是和你的项目匹配。如果遇到 SpringBoot 版本太高导致的不兼容问题,优先查看项目 pom.xml 里声明的版本,而不是自己随意升版本。
7.2 初始化数据库
在后端启动之前,必须先把数据库和表结构准备好。
# 登录 MySQL mysql -u root -p # 创建数据库,注意字符集 CREATE DATABASE driving_school DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 执行项目提供的 SQL 脚本 use driving_school; source /your_project_path/sql/driving_school.sql; # 查看表是否创建成功 show tables;如果项目提供的 SQL 脚本不全,也可以通过项目里的实体类和 Mapper XML 逆向推断出需要哪些表。这个步骤做完后,务必检查一下数据库账号密码是否和后端配置文件一致。
7.3 启动后端
在 IDEA 中打开后端项目,等待 Maven 依赖下载完成后,直接运行启动类。也可以在命令行启动:
cd driving-school-backend mvn spring-boot:run看到类似下面的日志,说明后端启动成功:
Tomcat started on port(s): 8080 (http) Started DrivingApplication in 5.123 seconds启动失败的排查顺序是:先看数据库连接配置,再看端口是否被占用,最后看 Maven 依赖是否完整。
7.4 启动前端
打开前端项目,先安装依赖:
cd driving-school-frontend npm install如果安装速度很慢,可以配置 npm 国内镜像源:
npm config set registry https://registry.npmmirror.com然后启动开发服务器:
npm run serve或者根据项目 package.json 的 scripts 配置选择对应的启动命令,常见的有 dev、start。前端启动成功后,终端会显示一个访问地址,通常是 http://localhost:3000 或 http://localhost:8081。
7.5 功能验证清单
系统运行起来后,不要只截图一个登录页就结束。建议按照下面的清单逐项验证,这也是你演示时可用的流程:
- 使用管理员账号登录,确认登录成功并跳转到后台首页。
- 在学员管理中新增一个学员账号,确认用户列表能显示新增数据。
- 使用学员账号登录,提交一条报名申请。
- 切换回管理员账号,在报名审核列表中看到这条申请并审核通过。
- 创建一节培训课程,并关联教练。
- 使用学员账号在课程时间内签到,确认考勤记录生成。
- 在考勤统计页面查看该学员的总学时是否正常。
- 退出登录后,直接访问需要认证的接口,确认 401 拦截生效。
任何一步失败,都要先定位是前端问题、后端问题还是数据库问题。最快速的方式是打开浏览器开发者工具的 Network 面板,看请求到底有没有发出去、返回了什么状态码。
8. 常见问题与排查思路
运行过程中遇到的问题虽然多种多样,但大多数集中在以下几个场景。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 后端启动失败,提示数据库连接拒绝 | 数据库服务未启动,或账号密码错误 | 使用数据库客户端连接测试 | 启动 MySQL,修改 application.yml 配置 |
| 前端页面打开后接口全部 404 | 前端 baseURL 与后端端口不一致 | 查看浏览器 Network 面板的请求地址 | 统一 baseURL 为后端实际端口 |
| 浏览器报 CORS policy 错误 | 后端未配置跨域,或配置不生效 | 查看浏览器 Console 具体提示 | 在后端配置 CorsFilter |
| 前端 npm install 报错 | Node 版本与项目不兼容,或依赖源不稳定 | 查看 npm 版本,查看具体报错信息 | 升级或降低 Node 版本,切换镜像源 |
| 后端启动成功,但端口被占用 | 上一次运行进程未关闭 | 查看端口占用进程 | 结束占用进程,或修改 server.port |
| 登录后刷新页面就退出登录 | Token 只存在内存中未持久化 | 检查前端 store 的 token 存储方式 | 将 Token 存储在 localStorage |
| 数据库中文乱码 | 数据库字符集不是 utf8mb4 | 查看表字符集 | 创建库时指定 utf8mb4 |
| 接口报 500 错误 | 后端代码异常,通常是 SQL 错误 | 查看后端控制台 SQL 日志 | 根据异常信息修正 SQL 或字段映射 |
这里要特别说一个容易忽略的点:很多接口 500 错误不是代码逻辑问题,而是实体类和数据库表字段对不上。比如数据库字段是 create_time,Java 属性是 createTime,如果没有开启 map-underscore-to-camel-case,就会导致查询失败。排查这类问题,优先看控制台打印的 SQL,问题往往一眼就能发现。
9. 答辩高频问题与工程实践建议
项目跑通了,只是完成了一半。毕业设计的另一半是答辩表达。以下问题是评委在驾校报名与考勤系统这类项目中经常追问的,建议提前准备。
第一个问题:为什么做前后端分离?这是一个送分题,但很多学生回答不好。你可以从职责划分、并行开发、独立部署、技术趋势四个角度展开。前端负责页面渲染和交互,后端负责数据处理和业务逻辑,两者通过接口通信。
第二个问题:登录认证是怎么实现的?如果回答只是“用了 Token”,大概率会被追问 Token 和 Session 有什么区别。Session 存储在服务器端,基于 Cookie 保持会话;Token 存储在客户端,服务端无状态。Token 更适合前后端分离架构,因为前端不一定在浏览器环境运行,而且可以独立验证请求身份。
第三个问题:数据库为什么这样设计?你需要能够解释每张表的用途和表之间的关联关系。尤其要说明为什么用户表要和学员扩展表分开,为什么报名记录独立建表而不是直接写在用户表里。能从职责单一、减少数据冗余、便于扩展三个角度回答,就已经高于平均水平。
第四个问题:考勤数据如何统计?你可以说明:考勤表记录了每次课程的签到和签退时间,按报名记录 ID 分组汇总出勤次数,再乘以单次课程学时,就可以得到总学时。这里也可以说,实现中支持按时间范围查询明细。
第五个问题:如何防止重复签到?可以回答两个层面:代码层面校验本次课程是否已经存在签到记录,数据库层面还可以对 enrollment_id 和 course_id 建唯一索引兜底。
除了答辩准备,工程实践上也有几点建议值得落实。
第一,密码不允许明文存储。项目里的初始化数据通常有预设账号,但只要涉及用户表,就应该使用 BCrypt 加密。论文里可以专门写一节关于系统安全的内容,很加分。
第二,接口统一返回 Result 对象。前后端分离项目中,统一的返回值结构非常重要。常见结构是 code、message、data 三个字段。后续维护接口或者写接口文档都会方便很多。
第三,操作前先备份。如果你要在自己的练习环境里修改数据库表结构或删除数据,建议先导出 SQL 备份文件。这不是危言耸听,很多同学改表的时候把整张表删了,又不知道怎么恢复。
第四,演示账号准备充分。答辩现场最容易翻车的场景是忘记账号密码。建议准备一个管理员账号、一个教练账号、一个学员账号,并且预置少量演示数据,保证报名审核、考勤打卡等操作可以直接演示。
第五,隐私字段注意脱敏。身份证号、手机号这类信息在页面列表展示时,可以做部分脱敏处理,比如只显示后四位。这个点在论文和答辩中都可以作为安全设计来介绍。
10. 总结与后续学习方向
驾校报名与考勤系统看起来是一个并不惊艳的业务管理系统,但你怎么把它讲清楚、怎么把它做扎实,决定了它在毕业设计中的价值。技术不在于多新,而在于你是否理解了每个设计决策背后的原因。为什么用 JWT 不用 Session,为什么报名记录单独建表,为什么考勤要防止重复签到,这些看起来很小的点,恰恰是答辩现场区分“真正做过”和“代码搬运”的关键。
如果你已经跑通了这个项目,下一阶段的进阶方向可以集中在四个方面:一是把 Spring Security 和 JWT 的整合原理吃透,而不只是调用现成工具类;二是引入 Redis 缓存,把 Token 和常用数据放到缓存中,理解缓存与数据库的一致性;三是给系统增加文件上传功能,实现身份证照片和驾照照片的存储与预览;四是为学时统计生成图表,用 ECharts 做展示,让系统在视觉上更有说服力。
把这个系统中任何一个模块做到比同学深入一步,比如在事务回滚、并发打卡、权限控制上做一个完整的故障演示,你的毕设答辩就会有一个非常稳定的亮点。做毕设不要追求大而全,要追求能讲清楚、能演示、能经得住追问。驾校报名与考勤系统提供的正是这样一套合适的练习场景。