基于SSM+Vue的课堂考勤系统设计与实现:从架构到部署全流程解析
2026/9/24 20:30:26 网站建设 项目流程

如果你正打算做课堂考勤这个课题,或者已经被老师分配了这个题目,你先别急着去网上找代码直接改。这个题目看着经典,但越经典的题目越容易做成一锅粥:功能东拼西凑、前后端接口对不上、论文和代码说的是两套东西。我这两年陆续带过几个用 SSM + Vue 做课堂考勤的毕设项目,踩过不少坑,也总结出了一套还算靠谱的落地路径。这篇就把完整思路、表结构、核心签到逻辑、Vue 端配置、论文写法、联调部署一次说清楚,适合本科毕设、课程设计,也适合想快速搭出一套前后端分离考勤系统的人。


1. 内容整体设计与选题思路

1.1 课堂考勤系统到底要做哪些事

先别动代码,把需求盘清楚。课堂考勤系统的核心角色一般是三类:管理员、教师、学生。

  • 管理员:管理教师信息、学生信息、课程信息,生成统计报表,系统日志查看。
  • 教师:创建课程、发起一次签到、查看某个班某次课的到课情况、期末导出出勤率。
  • 学生:查看自己已选课程、进行课堂签到、提交请假申请、查看个人考勤记录。

功能上最常见的做法是:教师端开启签到,生成一个签到码或二维码;学生输入签到码或扫码完成签到;后端记录签到时间、签到方式、地理位置(可选)等,并计算是否迟到。在此基础上,还可以扩展请假审批、补签申请、考勤统计图表、邮件通知、课表管理等功能。毕设项目不建议贪多,核心闭环做好,再挑两个扩展点做亮,就足够支撑论文的“创新点”章节了。

1.2 为什么是 SSM + Vue 而不直接上 Spring Boot

先说结论:从纯技术角度看,Spring Boot 确实更省事,自动配置、内嵌 Tomcat,几分钟能跑起来。但毕业设计是学术评价的一部分,很多院校的评分标准里明确要求展现“对各层框架的理解”。SSM(Spring + Spring MVC + MyBatis)的优势在于:

  • 分层非常清楚,Controller、Service、Mapper 各司其职,论文中画架构图、数据流图都更好画。
  • 配置文件是显式的,你对数据库连接、事务管理、Mapper 扫描都看得见摸得着,答辩被问“讲讲这个配置是干嘛的”也不容易慌。
  • 参考资源多,网上 SSM 整合案例、报错解决方案比某些偏门框架丰富得多。

Vue 作为前端框架,原因是更直白的:前后端分离是当前主流,Vue 生态成熟,Vue Router、Vuex/Pinia 都有大量实践文章。而且 Vue 对新手友好,比 React 的学习曲线稍缓,热重载也快,页面改完立马能看到效果。

这里补充一点,如果你导师不限制技术栈,Spring Boot + Vue 做起来更顺手,论文里一样能写“Spring 家族技术”。但如果你用的就是 SSM,请务必把 web.xml、spring-mvc.xml、spring-mybatis.xml 这几份配置吃透,答辩几乎必问。

1.3 前后端分离架构的边界划分

SSM + Vue 的项目天然是前后端分离的:后端只提供 RESTful 接口,返回 JSON;前端用 Vue 组件渲染页面,通过 Axios 发异步请求。

在设计阶段就要把边界划好,不然联调的时候会互相甩锅。我的习惯是:

  • 后端接口统一以 /api 开头,例如 /api/user/login、/api/attendance/start。
  • 所有返回数据统一用 { code, message, data } 这种结构包装。
  • 状态码约定清楚:200 成功、401 未登录、403 无权限、500 服务器异常。
  • 前端不做业务逻辑判断,只负责把后端返回的 code 翻译成用户能看懂的语言。

这样约定之后,前后端可以并行开发。后端用 Postman 测接口,前端用 Mock 数据先画页面,最后联调时只要关注字段对没对上,省掉大量 Debug 时间。


2. 后端 SSM 框架整合与核心业务实现

2.1 SSM 整合的核心配置

SSM 整合的关键就三份 XML 配置加一个 web.xml,但很多人在这块卡住。先说 maven 依赖,pom.xml 里除了 spring-webmvc、mybatis、mybatis-spring、mysql-connector-java,务必加上 druid 连接池和 jackson-databind,不然返回 JSON 会报错。

spring-mybatis.xml 里主要配数据源、SqlSessionFactory、Mapper 扫描器和事务管理器。一个比较常规且不会出错的写法如下:

<context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.example.entity"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

spring-mvc.xml 里核心是开启注解驱动和组件扫描,同时把静态资源和 JSON 消息转换器配好:

<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper" ref="jacksonObjectMapper"/> </bean> </mvc:message-converters> </mvc:annotation-driven> <context:component-scan base-package="com.example.controller"/>

很多同学踩过的一个坑是:Spring 容器和 Spring MVC 容器重复扫描同一个包,导致事务失效。正确做法是 Spring 的扫描排除 Controller,Spring MVC 的扫描只包含 Controller。

2.2 数据库表设计

考勤系统的表不用太复杂,但一定要满足三范式并支持业务闭环。我常用的表设计如下:

表名核心字段说明
t_userid, username, password, role, name, email统一用户表,用 role 区分管理员/教师/学生
t_courseid, course_name, teacher_id, start_time, end_time, location课程信息
t_student_courseid, student_id, course_id学生选课关联表
t_attendanceid, course_id, student_id, attendance_date, status, sign_time, sign_type考勤记录表
t_leaveid, student_id, course_id, reason, status, apply_time请假表

这里有个设计取舍:学生和老师分开建表是很多教程的做法,但增量扩展和权限判断比较麻烦。我习惯用一张 t_user 表加 role 字段,前端菜单和接口权限都用 role 控制,后续加管理员角色也方便。

考勤表里的 status 建议用整数存储:0 代表缺勤,1 代表正常,2 代表迟到,3 代表请假。用整数存的优势是统计 SQL 好写,例如统计某门课到课率时直接 SUM(status = 1) 就行。sign_type 可以记为 1 表示扫码签到,2 表示输入签到码,3 表示教师代签,这个字段主要是为了论文里写“支持多种签到方式”时有数据支撑。

建表时还要注意给 course_id 和 student_id 建联合索引,否则后面统计报表时数据量稍大就会出现慢查询。别笑,我的一个学生就是因为没建索引,到答辩演示的时候查一次考勤统计等了三四秒,场面一度很尴尬。

2.3 签到核心逻辑与接口实现

签到的核心逻辑并不复杂,但必须考虑边界情况。教师发起签到时,后端生成一个 4 到 6 位的随机签到码,设置有效时长,默认 5 分钟,存入 Redis 或内存缓存。学生提交签到码时,后端做以下判断:

public Result startSign(Integer courseId, Integer teacherId, int duration) { Course course = courseDao.selectById(courseId); if (course == null || !course.getTeacherId().equals(teacherId)) { return Result.error("课程不存在或无权操作"); } String signCode = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); // 实际开发中用 redis 存储,key 为 "sign:" + courseId,value 为签到码 signCache.put(courseId, signCode, duration); return Result.success("签到已开启", signCode); }

学生提交签到接口,除了校验签到码是否正确,还要判断当前时间是否在有效范围内,以及该学生是否已经签到过,防止重复提交:

public Result doSign(Integer courseId, Integer studentId, String signCode) { String cacheCode = signCache.get(courseId); if (cacheCode == null) { return Result.error("签到已结束"); } if (!cacheCode.equals(signCode)) { return Result.error("签到码错误"); } int count = attendanceDao.countToday(courseId, studentId); if (count > 0) { return Result.error("您已签到,请勿重复提交"); } Attendance record = new Attendance(); record.setCourseId(courseId); record.setStudentId(studentId); record.setAttendanceDate(new Date()); record.setSignTime(new Date()); // 判断是否迟到:课程开始时间前5分钟到课程开始后20分钟内算正常 record.setStatus(judgeStatus(courseId)); record.setSignType(1); attendanceDao.insert(record); return Result.success("签到成功"); }

关于“迟到”判定,别在代码里写死规则。因为不同学校对迟到的定义不一样,我建议把这个规则放到配置表或者课程表的字段里,由教师创建课程时设定,比如“上课前5分钟到上课后15分钟内都算正常”。这样论文里还能多写一个“灵活考勤规则设置”的功能点,答辩时能讲的东西一下子又多了一个。


3. 前端 Vue 环境搭建与功能实现

3.1 Vue 环境配置和项目创建

前端这一块,很多同学上来就卡在环境上。首先要确保本机装了 Node.js,建议装 LTS 版本,作者经常看到有人因为 Node 版本过新导致 node-sass 装不上,然后整个人心态崩掉。装完 Node 之后,npm 会自动带上,然后把 npm 源切到国内镜像,装依赖能快一大半:

npm config set registry https://registry.npmmirror.com

Vue CLI 创建项目,或者直接用 Vite 都可以。如果你用的教材和网课是 Vue CLI 那一套,建议按 Vue CLI 来,因为报错案例多,资料好查:

npm install -g @vue/cli vue create attendance-web

创建过程中会让你选预设,选 Manually select features,然后勾选 Babel、Router、Vuex、CSS Pre-processors。创建完成后,进入目录再装 Axios 和 Element UI(或 Element Plus,取决于你 Vue 是 2 还是 3)。这里我提醒一下,很多教学楼 Wi-Fi 环境下载慢,装依赖报错大概率是网络问题,清理缓存重试,或者换镜像源,千万不要一股脑去卸了重装。

安装完成后,熟悉一下目录结构。src/router 里配置路由,src/api 里封装接口请求,src/views 里放页面组件。还有一个细节:Vue Devtools 插件一定要装,调试组件状态和路由跳转能省一半时间,作者见过太多学生用 console.log 调试响应式数据,最后被搞崩溃的。

3.2 路由设计与登录鉴权

考勤系统按角色分页面,所以路由要区分管理员、教师、学生三种视角。我的做法是:

  • 登录页是公开路由。
  • 登录成功之后,根据返回的 role 字段动态拼接该角色对应的路由表,用router.addRoutes动态添加(Vue 3 里是router.addRoute)。
  • 全局前置守卫里统一判断:没有 token 就跳登录页,有 token 但访问的路由不在当前角色权限内就跳 403 页。
router.beforeEach((to, from, next) => { const token = sessionStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })

这里要注意一个坑:动态路由配合路由守卫时,刷新页面会导致动态添加的路由丢失,所以刷新后要重新拉取用户信息并重新挂载路由。常见的解决办法是把用户信息和路由表缓存到 sessionStorage,刷新时先读缓存再 addRoute。

3.3 Axios 封装与跨域问题

Axios 封装的核心有两点:统一处理请求头里的 token,统一处理 HTTP 错误和业务错误。我常用的一个封装模板:

import axios from 'axios' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config => { const token = sessionStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { return Promise.reject(error) } ) export default service

跨域问题是前后端分离项目的必经之路。后端就是 SSM 的项目,最简单的方式是在 Controller 或拦截器里统一加 CORS 响应头,或者直接用 Spring MVC 的@CrossOrigin注解。但我建议前后端联调阶段使用 vue.config.js 里的 devServer.proxy 做代理,这样浏览器看到的请求是同源的,省去很多跨域带来的奇奇怪怪的问题:

devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样配置之后,前端代码里请求 /api/xxx 就会转发到后端的 localhost:8080。等部署上线时,再把后端接口放到同一域名下,或者用 Nginx 反向代理,前端代码基本不用改。

3.4 考勤签到页和统计报表页实现

签到页是学生最重要的页面。我的实现思路是:

  • 页面加载时调用后端 /api/attendance/current 接口,查询当前用户所有正在签到中的课程。
  • 每条课程显示课程名称、教师、上课地点、签到剩余时间,旁边放一个“签到”按钮。
  • 点击签到后弹窗让用户输入签到码,提交到 /api/attendance/sign。
  • 签到成功后按钮置灰,并显示签到成功的时间,防止短时间重复点击造成重复提交。

页面上的倒计时可以用 setInterval 实现,但要注意在组件销毁时清除定时器,否则页面切走之后回调依然在执行,控制台报错不说,还容易造成内存泄漏。

统计报表页面建议用 ECharts 画柱状图和饼图:柱状图展示某门课最近 10 次课的出勤人数变化,饼图展示某次课的正常/迟到/缺勤/请假占比。ECharts 的引入方式不复杂,直接 npm 安装后在组件里引入即可,但要注意图表容器需要有明确的高度,否则图表渲染不出来,这个是新手高频踩坑点。


4. 论文架构与撰写方法

4.1 论文章节结构与每个章节该写什么

毕设论文一般都不少于 1.5 万字,常见的结构是:

章节内容建议篇幅
摘要系统背景、主要功能、采用技术、测试结果300字左右
第1章 绪论课题背景与意义、国内外研究现状、主要工作2000字以上
第2章 需求分析可行性分析、功能需求、非功能需求、用例图2500字以上
第3章 系统设计架构设计、功能模块设计、数据库设计、E-R图3000字以上
第4章 系统实现各模块实现截图、核心代码片段、关键逻辑说明3500字以上
第5章 系统测试测试环境、功能测试用例表、界面测试、性能测试1500字以上
总结与展望系统解决了什么问题、还存在哪些不足、后续方向800字左右

很多同学容易把第4章写成代码粘贴大会,一整页整页贴代码,导师看一眼就烦。正确做法是每个功能模块先放截图,再贴 10 到 20 行核心代码,然后用自然语言解释这段代码解决了什么问题。比如签到模块,贴判断签到码和重复签到的那段逻辑就够了,不要从 Controller 到 Mapper 全贴一遍。

需求分析章节里,用例图、类图、流程图是重头戏。用 Visio、ProcessOn 或者 draw.io 画清楚即可,注意画图风格统一,别一个图是黑白的一个图是彩色的,这些细节评审老师很在意。

4.2 论文与代码如何保证一致

我见过最离谱的情况是:论文里数据库表有 8 张,实际代码里只建了 5 张;论文里写了“支持签到码和二维码两种签到方式”,代码里只有签到码输入框;论文的架构图画的是 Spring Boot + Redis,代码却是纯 SSM。这种硬伤在答辩时被老师翻出来,基本等于提前送命。

我的建议是,论文和开发同步进行,不要等代码写完了再补论文。开发每完成一个模块,就顺手把该模块的截图、表结构、核心逻辑写进论文草稿里。等开发完成,论文的初稿其实就已经成型了大半,后面只需要润色和补充测试章节就行。

另外,论文中的每个功能点都必须在代码里能找到对应实现,代码里的每个核心类都要能在论文的模块设计里找到位置。哪怕是一些很小的功能,比如“教师可以导出 Excel 考勤表”,论文设计章节写了,代码里就一定要有这个导出按钮,不然就是给自己挖坑。

4.3 答辩演示准备的几个要点

答辩演示环节比很多人想象的重要,作者听过太多“功能做好了但演示翻车”的案例。几个经验:

  • 准备一套干净的演示数据,不要用测试时插入的“测试1”“测试2”这类脏数据。演示前把数据库恢复到初始状态,各项数据要看起来真实、规整。
  • 提前把各角色账号准备好:管理员账号、教师账号、学生账号各一个,密码统一设成容易输入的,别在台上因为密码打错被卡住。
  • 演示流程要提前走三遍以上,尤其是“教师发起签到 -> 学生扫码签到 -> 教师查看统计”这条主线流程,确保每一步都不会因为网络慢、缓存过期等情况挂掉。
  • 万一演示时后端崩了,代码层面临时修不好,还有一个终极大法:把所有核心页面截图放进 PPT 里,说“这是我在本地环境的执行效果”,虽然有点投机取巧,但至少不会在台上冷场。

还有一个答辩高频问题要提前准备:“你的系统和其他考勤系统相比,有什么优势或创新点?”如果你没做算法、没做大数据分析,就老老实实回答“本系统在业务流程上覆盖了课内考勤、请假、补签、统计的完整闭环,并在界面交互上针对三种角色进行了设计优化”,比硬吹“基于人工智能的考勤分析”靠谱得多。


5. 前后端联调、部署与常见问题排查

5.1 前后端联调的常见卡点

前后端联调阶段,大部分时间不是在写代码,而是在对着接口文档核对字段。最常见的问题:

  • 后端返回的时间是 Date 对象,前端拿到的是时间戳字符串,显示格式不对。解决办法是后端在实体类的日期字段上加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解。
  • 后端接口返回的字段用的是下划线命名(如 student_name),前端 JSON 里用的是驼峰命名(studentName),导致页面渲染为空。解决办法是 MyBatis 配置里开启mapUnderscoreToCamelCase,或者在接口统一转一次。
  • 分页查询时,前端传 pageNum 和 pageSize,后端返回的 total 是总数,但是前端忘了在表格组件里配置 total 的来源,导致分页组件总显示 1 页。这类问题只要提前约定好返回结构就不会出现。

我的习惯是写接口时同时维护一份简单的接口文档,不用 Swagger 那么重,用 Markdown 或者在代码注释里写好也行,至少包含 URL、请求参数、返回示例。有了这个文档,前后端并行开发时基本不会因为字段名反复扯皮。

5.2 部署打包和环境问题速查

项目做完了要提交或者部署到服务器,后端打成 war 包丢进 Tomcat,或者打成 jar 包直接因为有内嵌容器。但如果你用的是传统 SSM + 外部 Tomcat,注意以下这些点:

  • 数据库连接配置不要写死 root 密码在代码里,建议放到 jdbc.properties,部署时只改配置文件就行。
  • 文件上传功能如果是写在本地磁盘,部署到服务器后要留意目录是否存在、权限是否足够,否则上传图片会静默失败。
  • 前端npm run build打成静态文件后,直接放到 Tomcat 的 webapps 下也可以,但刷新页面会出现 404,因为 Vue Router 用的是 history 模式。解决办法有两个:一是把模式改成 hash,二是给 Tomcat 手动配置一个转发规则。毕设场景我建议直接用 hash 模式,虽然地址栏多一个 # 号,但省心很多。

下面把作者和学生实际操作中遇到的典型问题整理成一个速查表,直接照着排查就行:

现象可能原因解决办法
后端启动时报端口被占用8080 端口被其他程序占用改 Tomcat 端口或杀掉占用进程
启动时报 ClassNotFoundExceptionMaven 依赖缺失或版本冲突执行 mvn clean package 后重新导入,检查依赖树
连接数据库报 Access denied数据库账号密码错误或权限不足核对 jdbc.properties,本地 MySQL 重启后重试
MyBatis 查询结果全是 null表字段与实体类属性对不上开启 mapUnderscoreToCamelCase 或使用别名
前端 npm install 报错Node 版本太高或太低、网络问题使用 LTS 版本,切换镜像源,删除 node_modules 重装
跨域请求被拦截后端未加 CORS 配置使用 devServer proxy 或后端加 @CrossOrigin
前端页面空白路由配置错误、js 引入路径不对检查控制台报错,确认 publicPath 配置
idea 里 import 的包标红Maven 没有自动导入依赖Maven 面板点击 Reload All Projects

5.3 线上演示环境的备份与回滚

毕设答辩前,最好把项目完整跑在一个稳定的演示环境里。我的经验是数据库一定要提前备份,导出 SQL 文件放到网盘或者 U 盘。答辩现场经常会发生老师随意点了几个按钮,把测试数据改乱或者删掉的情况,如果数据库没备份,当场恢复数据非常狼狈。

备份数据库就一条命令的事:

mysqldump -u root -p attendance_db > attendance_backup.sql

恢复也简单,把备份文件导进去就行。另外,演示环境建议禁用“删除成绩”“清空考勤”这类高风险按钮,或者至少加一个二次确认弹窗。这不是逃避问题,而是降低演示出错概率的合理设计。

还有一个小细节,代码里所有涉及本地绝对路径的地方,比如图片上传路径、日志目录,都改成相对路径或者通过配置读取。否则换一台电脑运行项目,这些莫名其妙的地方就会报错,还不好排查。


最后分享一点个人经验,毕设做课堂考勤这个题,最大的陷阱不是技术难度,而是“一眼看去太简单”。我建议你在完成基础功能之后,至少增加一个有区分度的扩展点,比如:导出月考勤汇总 Excel、课程维度的出勤趋势图表、迟到早退规则自定义、课表导入解析,或者按班级批量导入学生名单。这些功能工作量不算大,但能让你的系统从“例子”变成“作品”,论文里的亮点更充分,答辩时也更有底气。选题常见不要紧,能让一个常见题目做出完整的工程质量和清晰的业务闭环,本身就是合格毕业设计的标准。

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

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

立即咨询