☰
SpringBoot+Vue3考勤系统开发实战:前后端分离架构与权限设计解析
2026/10/22 14:42:36 网站建设 项目流程

考勤系统这类项目,做过的人都知道,真正的难点不在“打卡”这个动作本身,而在于角色权限的梳理、考勤规则的设计,以及一堆统计报表怎么算得清楚。最近我正好带一个模拟项目X用了SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0这套组合重写了一个大学生考勤系统,过程中踩了不少坑,也积累了一些经验。今天不聊虚的,就把这个项目的架构思路、核心模块、数据库设计、前后端对接要点,连同本地部署步骤和常见问题排查一起捋一遍,想自己搭类似系统的朋友可以直接对照着参考。

这个项目本身是一个典型的教学级完整项目,适合正在学Java Web的开发者、准备做毕设的学生,或者想了解前后端分离项目完整落地流程的初级工程师。它的价值在于:技术栈主流、业务场景清晰、代码结构完整,而且自带文档,改一改就能扩展成别的管理系统。

1. 项目全景与核心价值拆解

1.1 这个考勤系统到底解决什么问题

很多高校目前还在用纸质签到或者Excel汇总的方式来记录课堂出勤,辅导员和任课老师月底对账对到头疼。大学生考勤系统的核心任务,就是把“学生签到、教师确认、辅导员统计”这条链路搬到线上,让每个人只看到自己该看的数据,同时把请假、补签、迟到早退这些异常情况统一管理起来。

模拟项目X整体分为三类角色:学生端、教师端、管理员端。学生可以查看自己的考勤记录、发起请假申请;教师可以给自己授课的班级录入考勤、审批请假、查看班级统计;管理员负责维护班级、课程、用户账号等基础数据。三个端共用一套后端接口,通过JWT做身份认证,再通过前端路由守卫控制页面访问权限。

1.2 面向人群与学习价值

这类项目对学习者的价值不只是“会写代码”,更重要的是通过一个完整业务场景理解CRUD之外的工程化细节:比如如何设计表结构才能支持多角色查询、如何封装统一响应格式让前端少写判断、如何用拦截器做登录鉴权、如何用AOP记录操作日志。这些都是平时写小Demo练不到的。

另外,因为项目中包含了完整的文档、SQL脚本和部署说明,拿来作为课程设计或者毕业设计的参考是非常合适的。我自己在实际拆解这套代码时,发现它的模块划分很清晰,扩展起来不费劲,想加一个“刷脸打卡”或者“GPS定位签到”功能,都可以在现有架构上平滑接入。

2. 前后端分离架构与技术选型背后逻辑

2.1 为什么后端选SpringBoot2而不是SpringBoot3

SpringBoot3出来有段时间了,但很多生产环境还在用SpringBoot2,原因很现实:生态稳定、资料多、踩坑成本低。这个项目选择SpringBoot2.7.x,配合JDK1.8,可以避开SpringBoot3强制JDK17带来的升级问题。如果你的电脑上装的是JDK1.8,那直接用SpringBoot2是最省事的路径。

SpringBoot2的关键特性在这个项目中体现得很集中:内嵌Tomcat省去外部容器配置,自动装配让数据源、MyBatis、事务管理等开箱即用,配合YAML配置文件可以快速切换开发和生产环境。整个项目的配置非常简洁,核心就是一个application.yml文件。

2.2 Vue3配合Vite的现代化前端开发体验

前端选择Vue3而不是Vue2,除了版本趋势外,最直观的体验是Composition API带来的逻辑复用能力。比如组合式函数可以把“获取当前登录用户信息”的逻辑做成一个useUserInfo(),多个页面调用同一份逻辑,代码清爽很多。

项目用Vite作为构建工具,启动速度比Webpack快一个量级。实际开发时,前端跑在5173端口,后端跑在8080端口,通过Vite的代理配置把/api前缀的请求转发到后端,避免了开发环境下的跨域烦恼。Vue3项目整体还搭配了Element Plus组件库,表格、表单、弹窗这些后台管理界面常用组件几乎不用自己写样式。

2.3 MyBatis-Plus如何提升单表CRUD效率

MyBatis-Plus的价值在于,单表操作几乎不需要写SQL。内置的BaseMapper提供了selectById、selectList、insert、updateById等现成方法,配合LambdaQueryWrapper可以写出类型安全的条件查询,比如统计某门课程某次课缺勤的学生名单,一行链式条件就能搞定。

// 统计某课程某次课的缺勤学生 List<Attendance> absentList = attendanceMapper.selectList( new LambdaQueryWrapper<Attendance>() .eq(Attendance::getCourseId, courseId) .eq(Attendance::getScheduleId, scheduleId) .eq(Attendance::getStatus, 0) );

这段代码如果手写XML,至少多出十几行。MyBatis-Plus还有一个很实用的功能——字段自动填充,创建时间和更新时间可以设置@TableField(fill = FieldFill.INSERT),插入时自动写入当前时间,不用每个插入操作都手动赋值。

2.4 MySQL8.0与数据库初始化策略

MySQL8.0和5.7相比,默认字符集变成了utf8mb4,对生僻字和emoji的支持更好。项目里数据库连接串需要指定serverTimezone=Asia/Shanghai并开启useSSL=false,否则启动时会报时区错误。

系统初始化靠一个init.sql脚本,里面包含建库、建表、插入初始账号数据。这种做法比用Flyway之类的迁移工具更轻量,适合教学项目。脚本中把所有表的ENGINE都指定为InnoDB,字符集统一设置utf8mb4,避免中途出现中文乱码问题。

3. 核心模块拆解与数据库设计思路

3.1 用户、角色与权限控制

考勤系统涉及三类用户,但数据库层面我没有设计三张独立的用户表,而是统一放在一张sys_user表里,通过role字段区分。这样做的好处是登录接口可以统一处理,权限控制逻辑也更集中。

表结构大概包含这些字段:id、username、password、real_name、role(2表示管理员、1表示教师、0表示学生)、class_id(学生所属班级,教师和管理员为空)、create_time。

密码存储用的是BCrypt加密。很多人图省事直接用MD5,但MD5是可以暴力破解的,安全等级完全不够。Spring Security的BCryptPasswordEncoder加盐加密,相同密码每次加密结果都不同,即使数据库泄露也不容易被反查。

后端权限控制基于自定义拦截器,登录后签发JWT令牌。前端则通过Vue Router的全局前置守卫检查本地存储的token,没有token直接跳转到登录页。这种双重校验的思路值得好好学习,前端控制的是体验,后端控制的是安全,二者缺一不可。

3.2 考勤打卡模块的实现思路

打卡是系统的核心业务,但实现上比想象中的要简单。核心表attendance包含以下字段:id、student_id、course_id、schedule_id、check_time、status、remark。

这里的schedule_id对应“某课程某次课”,而不是存日期时间。为什么?因为学期内的课程安排是有规律的教学周,比如“第10周周一的第3节”,存储成课程表记录更规范,也方便后续处理调课、停课等特殊情况。这是设计上比较赞的一个点。

学生打卡时,后端做的事情就是校验三件事:当前时间是否在本次课的打卡窗口内、该学生是否选了这门课、这次课是否已经打过卡。校验通过后插入一条记录并返回打卡结果;如果迟到了,时间比对会直接给出对应状态。所以打卡的业务代码其实没有复杂的并发处理,用事务注解加唯一索引兜底就够了。

3.3 考勤统计与报表可视化

考勤系统的最终价值是统计数据,不是把数据堆在页面里让用户自己数。项目提供按课程、按班级、按学生三个维度的统计接口,返回的是“出勤率、缺勤率、迟到次数、请假次数”这种指标。

统计报表的核心SQL并不复杂,比如统计某学生的出勤情况,可以用条件聚合一次查出多个维度的结果:

SELECT COUNT(*) AS total, SUM(status = 1) AS normal_count, SUM(status = 2) AS late_count, SUM(status = 3) AS leave_count, SUM(status = 0) AS absent_count FROM attendance WHERE student_id = ?

前端拿到这些数据后用ECharts绘制饼图或者柱状图,教师端能直观看到班级整体出勤走势。对于辅导员来说,这个功能比单纯打卡重要得多,一个学期下来谁经常缺勤,图表上看得一清二楚。

3.4 班级、课程与选课关系梳理

数据库设计中还有一个不可忽略的部分——班级、课程、学生之间的关联关系。系统中有三张基础表:class_info(班级)、course(课程)、student_course(选课关系表),另外还有teacher_course(教师授课关系表)。

选课关系表是关键,它决定了学生端打卡时能通过student_id查到自己选了哪些课程,也能确保教师只能给“自己授课的班级”录入考勤。这种多对多的关联通过中间表解耦,是关系型数据库设计里最常见的模式,也是这个项目里比较值得琢磨的部分。

4. 关键接口与前后端对接实现拆解

4.1 统一响应体设计

前后端分离项目最怕接口返回格式五花八门。项目里封装了一个统一的Result类,所有接口都返回{code, message, data}结构。成功时code为200,失败时为业务错误码,比如401表示未登录、403表示无权限、500表示服务器异常。

前端封装了request.js工具类,基于Axios实现。响应拦截器里判断code,code为401时自动清除本地登录信息并跳转到登录页。这样一个全局统一处理,能省掉特别多重复代码。

4.2 JWT登录流程与令牌刷新

登录接口调用成功后,后端返回一个token字符串,前端把它存进localStorage。后续每个请求都在Axios请求拦截器里加上Authorization: Bearer {token}头。

后端用一个JWT工具类负责生成和解析token,载荷里存放userId和role。拦截器里每次请求都解析token,如果解析失败直接返回401。token设置2小时过期,对于考勤系统这种轻量场景,加上一个“记住我”的功能即可实现自动续期,不需要引入OAuth2那套重逻辑。

4.3 分页查询与条件筛选

学生列表、考勤记录这类数据量较大的接口,都用了MyBatis-Plus的分页插件。分页插件只需要在配置类里注册一个PaginationInnerInterceptor,查询时传入Page对象就会自动拼接LIMIT语句。

// 教师端按课程分页查询考勤记录 Page<AttendanceVO> page = new Page<>(current, size); LambdaQueryWrapper<Attendance> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Attendance::getCourseId, courseId); wrapper.orderByDesc(Attendance::getCheckTime);

条件筛选通过前端传参、后端拼接LambdaQueryWrapper实现。比如教师想筛选“只看迟到的学生”,额外传入status=2即可,不用为每个组合条件单独写接口。

4.4 前端路由与权限守卫

前端页面包括登录页、管理员布局、教师布局、学生布局,对应的路由都挂载在各自的父路由下。Vue Router的全局前置守卫中,做个简单判断即可:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else { next() } })

如果想进一步控制“学生不能访问管理员页面”,可以在路由meta里记录role,守卫里校验当前用户角色与meta是否匹配,不匹配则重定向到401页面或者个人首页。对于这个项目来说,前端权限做到“页面上”即可,后端接口拦截才是安全基线。

5. 本地部署三步走,从零跑起来

5.1 环境准备清单

想把这个项目跑起来,需要准备以下环境:

  • JDK 1.8及以上
  • Maven 3.6及以上
  • Node.js 16及以上
  • MySQL 8.0
  • IDEA开发工具(社区版即可)

版本之间需要互相兼容:JDK8配SpringBoot2.7没问题,Node16配合Vite4也稳定,不要盲目装最新版本,有时候版本冲突反而浪费时间。

5.2 数据库初始化与配置文件调整

启动前先创建数据库,用Navicat或者命令行执行init.sql脚本。然后修改后端application.yml中的数据源配置,重点检查四项内容:数据库地址、数据库名、用户名、密码。

spring: datasource: url: jdbc:mysql://localhost:3306/student_attendance?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: your_password

一个很常见的坑是:数据库密码包含特殊字符,比如@或#,在YAML文件中需要加引号包裹,否则启动时报解析错误。

5.3 后端启动和前端启动细节

后端代码导入IDEA后,等待Maven依赖下载完,直接运行主类的main方法。如果8080端口被占用,可以在配置文件中改成其他端口。

前端在项目根目录下执行:

npm install npm run dev

如果npm安装依赖特别慢,先执行npm config set registry https://registry.npmmirror.com再重新安装。前端启动后,浏览器访问http://localhost:5173,使用管理员账号登录即可看到系统主界面。

6. 实际部署中最容易遇到的6个问题

6.1 数据库连接失败,启动报错

数据库连接失败是最常见的启动报错,表现为红字提示Access denied for user或者Unknown database。先检查用户名密码是否拼写正确,再确认数据库名是否和配置一致。如果是Public Key Retrieval is not allowed,需要在连接串后加allowPublicKeyRetrieval=true。

6.2 前端请求接口404或403

页面能打开但数据出不来,第一反应看浏览器控制台。404说明前端请求的接口路径不对,检查代理配置里/api是否匹配到后端实际Controller的@RequestMapping。403则多半是没带token或者token过期,可以在控制台Network面板里确认请求头有没有Authorization。

6.3 Maven依赖下不动或者下载慢

IDEA中Maven默认使用中央仓库,国内网络拉取SpringBoot和MyBatis-Plus全家桶的依赖很慢。在settings.xml中配置阿里云镜像:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror>

配置完重新导入项目,依赖下载速度会明显提升。

6.4 npm run dev启动后页面空白

页面空白大概率是Node版本和Vite版本不匹配。Vite4要求Node18以上,如果电脑装的是Node14,打开页面会白屏并且控制台报错。升级Node版本或者把Vite降到3.x版本都能解决。

6.5 打卡时间判断不准确

打卡逻辑里判断迟到的时间,需要注意服务器时间和本机时间不一致的情况。最好在操作系统中统一时区,同时确保数据库连接串带上了serverTimezone=Asia/Shanghai。另外一个容易忽略的点是:Java的LocalTime比较是和数据库存的是字符串还是时间类型强相关的,如果数据库字段用datetime而代码用LocalDate,比较结果会出偏差。

6.6 多人同时打卡出现重复记录

虽然场景简单,但同一个学生狂点打卡按钮仍然可能产生重复数据。除了后端接口加status字段判断外,更保险的做法是在(student_id, schedule_id)上建唯一索引。数据库层面的约束永远比业务代码的if判断更可靠。

7. 这个项目还能怎么扩展

这套系统最值得肯定的地方是架构留了扩展空间,就算以后要加新功能,现有代码框架不需要大改。

比如添加“二维码打卡”功能,教师端每次生成一个临时二维码,学生扫码后请求后端接口,后端只需要把当前时间和二维码中的随机码做校验即可。再比如接入企业微信机器人推送考勤异常通知,只需要在统计接口返回异常数据后调用WebHook接口,半小时内就能完成。

我个人建议学习者在跑通这个项目后,可以试着加一个“考勤申诉”功能:学生被误标记为缺勤后提申诉,教师审批后自动修改状态。这个功能涉及新表设计、消息通知、状态流转,做完之后对业务系统的理解会更深一个层次。

我实际拆解这个项目的体会是:好的教学项目不是功能越多越好,而是把“业务、架构、代码”三个层次讲得刚刚好。这个考勤系统在这三者的平衡上做得不错,既有具体场景,也有规范性设计,不会让人淹没在复杂业务里忘了技术的核心。如果你正准备动手做一个前后端分离的完整项目,不妨先把这个系统的代码彻底吃透,再谈改造和创新。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询