☰
SpringBoot2+Vue3+MyBatis-Plus大学生考勤系统实战与部署详解
2026/10/2 2:51:23 网站建设 项目流程

开箱即用的大学生考勤系统,SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0这套组合现在几乎成了Java Web毕设和中小型管理系统的标配。这套源码我实际跑过一遍,前后端分离,文档齐全,从数据库脚本到部署说明都有,适合拿来直接改业务,也适合作为学习SpringBoot2和Vue3实操的参考项目。这篇把它的架构思路、核心代码逻辑、数据库设计、部署细节和踩坑记录都整理出来,希望能帮到正在做类似系统的朋友。

1. 项目整体设计与技术选型

1.1 为什么是SpringBoot2+Vue3而不是其他组合

拿到项目标题第一眼,最抓眼球的就是技术栈组合。这套源码没有选择传统的JSP+Servlet,也没用SpringCloud那套重框架,而是卡在了一个非常舒服的中间位置:SpringBoot2负责后端接口,Vue3负责前端页面,MyBatis-Plus帮我们省掉大量重复的DAO代码,MySQL8.0作为数据存储层。

先说SpringBoot2。相比Spring Framework的XML配置时代,SpringBoot2的核心价值是“约定大于配置”,内嵌Tomcat,一个main方法就能启动。这对大学生考勤系统这种体量的项目来说足够轻量,而且市面上大部分教学资源和毕设参考都是基于SpringBoot2的,遇到问题搜起来效率高。有人可能会问,为什么不用SpringBoot3?SpringBoot3要求JDK17起步,很多学校的机房和实验室环境还停留在JDK8,SpringBoot2 + JDK8的兼容性明显更稳妥,这也是很多成熟源码选择它的原因。

Vue3这边,组合式API(Composition API)带来的代码复用能力是Vue2没法比的。考勤系统里最典型的场景是“打卡”和“考勤记录查询”这两个页面,如果用Vue2的Options API,逻辑会散落在data、methods、computed里,代码一多就乱;而Vue3可以把打卡逻辑、倒计时逻辑、表格筛选逻辑全部封装成独立的组合式函数,每个页面代码清爽很多。另外Vite构建工具的加持,让前端启动速度和热更新体验比webpack时代快了一个量级。

MyBatis-Plus是这套系统里最省力的部分。它不像JPA那样全自动,也不像原生MyBatis那样需要手写大量XML。MyBatis-Plus通过继承BaseMapper,直接获得insert、deleteById、selectPage这些通用方法,分页插件也是插拔式的。对于考勤系统里最常见的“按学号查考勤记录”“按日期范围查统计”这类操作,几乎不需要写SQL,只要有实体类和Mapper接口就行。有句话叫“单表操作零SQL”,用在这类系统上很贴切。

最后是MySQL8.0。考试系统里的考勤数据通常包含学生表、课程表、考勤记录表、请假申请表,数据量不大但表间关系明确。MySQL8.0相比5.7的窗口函数、公用表表达式(CTE)和更好的UTF-8支持(utf8mb4默认字符集),让一些统计数据查询写起来方便很多。

1.2 项目的模块划分与核心业务逻辑

拿到源码后,先把目录结构过一遍。这套系统的模块划分很清晰,没有过度设计。后端主要分为几个包:

  • controller:接收前端请求,返回R(统一响应体)
  • service:业务逻辑层,比如考勤规则校验、请假审批状态流转
  • mapper:MyBatis-Plus的Mapper接口,配合实体类使用
  • entity:数据库实体映射
  • config:全局配置,包括跨域、MyBatis-Plus分页插件、JWT拦截器等

前端部分则是典型的Vue3 + Vite + Pinia结构,按功能拆分页面组件。src/router下是路由表,src/api下是接口请求封装,src/views下是页面级组件,比如Login.vue、Dashboard.vue、Attendance.vue、Leave.vue。

核心业务逻辑围绕“学生考勤”展开。最基本的流程是:老师导入或创建班级和学生信息,学生登录后点击“签到”按钮,系统记录签到时间和IP/位置信息,生成一条考勤记录;之后老师可以按日期、按课程、按学生维度去查询和导出考勤统计。如果学生请假,则走请假申请流程,老师审批后,请假时间内的缺勤不会标记为旷课,而是标记为“请假”。

这套系统的逻辑不算复杂,但胜在闭环完整。从用户登录、角色权限,到考勤打卡、数据统计,再到请假审批,每个环节都有对应的接口和页面,适合学习,也适合二次开发。

2. 后端核心实现:SpringBoot2服务从配置到业务接口

2.1 项目依赖与核心配置

先看pom.xml。SpringBoot2版本的父依赖通常是spring-boot-starter-parent,版本号在2.5.x到2.7.x之间。这个版本区间很关键,它决定了后续MyBatis-Plus和MySQL驱动的兼容性。我自己测试时用的是SpringBoot 2.7.14,配合MyBatis-Plus 3.5.3,跑起来没有版本冲突。如果你拿到源码后发现启动报错,八成是依赖版本之间不对齐。

核心依赖除了spring-boot-starter-web,还需要:

  • mybatis-plus-boot-starter:提供MyBatis-Plus的SpringBoot自动配置
  • mysql-connector-java:MySQL驱动,8.0.x版本对应mysql-connector-j 8.0.x
  • lombok:省去实体类的getter/setter,这个非常实用
  • jjwt或java-jwt:用于生成和校验JWT Token
  • spring-boot-starter-validation:用于参数校验

application.yml里的配置有几个值得注意的地方。数据源使用druid或HikariCP都可以,SpringBoot2默认的HikariCP性能已经很好,配置起来也简单:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/attendance_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你的密码

这段配置里的serverTimezone=Asia/Shanghai是SpringBoot2连接MySQL8.0最常见的坑。如果你的服务器时区不是中国标准时间,数据库连接会报时区错误。allowPublicKeyRetrieval=true是MySQL8.0+驱动的新要求,否则用caching_sha2_password认证时会出现连接失败。

MyBatis-Plus配置也不难:

mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml type-aliases-package: com.example.attendance.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true

map-underscore-to-camel-case这个配置必须打开,它能让数据库的student_id字段自动映射到实体类的studentId属性,省去一大串@TableField注解。

2.2 MyBatis-Plus通用CRUD与考勤业务的融合

MyBatis-Plus最大的好处是BaseMapper接口直接提供单表CRUD。我们看一下考勤记录实体:

@Data @TableName("attendance_record") public class AttendanceRecord { @TableId(type = IdType.AUTO) private Integer id; private Integer studentId; private Integer courseId; private LocalDateTime checkInTime; private String status; // 正常, 迟到, 缺勤, 请假 private String remark; }

对应Mapper接口:

public interface AttendanceRecordMapper extends BaseMapper<AttendanceRecord> { }

就这么简单,就能拥有selectById、insert、deleteById、selectPage等方法。但考勤系统不可能永远只做单表查询,比如“查询某学生某门课程的出勤率”,需要关联student和course表,这时候有两种选择:一种是写SQL,另一种是写业务逻辑联表。

这里更推荐在Mapper里写一个自定义SQL,用@Select注解或者XML文件。因为统计类SQL用Java代码拼不如SQL清晰,而且要利用MySQL8.0的窗口函数或GROUP BY能力。比如:

@Select("SELECT s.student_no, s.name, COUNT(r.id) AS total_days, " + "SUM(CASE WHEN r.status = '正常' THEN 1 ELSE 0 END) AS normal_days " + "FROM student s LEFT JOIN attendance_record r ON s.id = r.student_id " + "WHERE r.course_id = #{courseId} " + "GROUP BY s.id") List<Map<String, Object>> selectAttendanceStats(@Param("courseId") Integer courseId);

这种统计查询放在XML里更好,因为如果以后要调整统计维度,比如加入“按周分组”,改SQL比改Java逻辑快得多。MyBatis-Plus的分页插件配合Page对象使用:

Page<AttendanceRecord> page = new Page<>(current, size); Page<AttendanceRecord> result = attendanceRecordMapper.selectPage(page, new LambdaQueryWrapper<AttendanceRecord>() .eq(AttendanceRecord::getCourseId, courseId) .orderByDesc(AttendanceRecord::getCheckInTime));

分页配置类需要注册一个MybatisPlusInterceptor的Bean,否则分页不生效:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

2.3 登录鉴权与角色权限控制

后端接口不是所有人能随便调用的。系统里有三种角色:管理员、老师、学生。操作权限不同。这也是很多考勤系统被攻击的点,所以源码实现的JWT鉴权值得细看。

流程是用户登录成功后,后端生成一个JWT Token,存到前端localStorage。每次请求通过拦截器校验Token,解析出用户ID和角色,放入ThreadLocal或请求上下文。关键代码思路如下:

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (!StringUtils.hasText(token)) { throw new BusinessException("未登录"); } // 在校验时解析token,并检查是否过期 Claims claims = JwtUtil.parseToken(token.replace("Bearer ", "")); // 可在此处设置用户上下文 UserContext.set(claims); return true; } }

这里有个小细节:前端请求头里往往带Bearer前缀,后端拦截器记得去掉。如果源码里没做这个处理,会导致前端登录成功但所有请求都401,是一个高频bug。

权限控制上,简单的做法是在需要老师权限的接口上自定义@RequireRole注解,然后在拦截器或AOP里校验角色。更简单的做法是直接在Controller方法体里判断:

if (!"ROLE_TEACHER".equals(UserContext.getRole())) { throw new BusinessException("无权限操作"); }

对于大学生考勤系统,这种级别的权限控制够用了,不必引入Spring Security那套复杂配置。

3. 前端Vue3:从登录页到考勤看板的落地实战

3.1 Vue3项目结构与路由设计

Vue3项目的目录结构要不要细看?要。因为它决定了后面加功能时的效率。这套源码的src目录大概是这样的:

  • src/api:二次封装的axios请求模块
  • src/assets:静态资源
  • src/components:通用组件
  • src/router:路由配置
  • src/store:Pinia状态管理
  • src/views:页面组件
  • src/utils:工具函数

没有使用TypeScript,全部JavaScript,好处是上手门槛低。如果你拿到源码发现某些文件后缀是.vue,但用了<script setup>,说明这是Vue3的标准写法。

路由设计方面,去掉登录页,后台主框架是一个Layout组件,嵌套路由。例如:

{ path: '/attendance', component: Layout, children: [ { path: 'record', name: 'AttendanceRecord', component: () => import('@/views/attendance/Record.vue'), meta: { title: '考勤记录', roles: ['TEACHER', 'ADMIN'] } } ] }

这里面的meta.roles字段结合前端路由守卫,实现路由级权限控制。当学生登录后,路由表里没有“考勤管理”和“用户管理”的入口,体验上比后端返回401再提示要友好得多。

组件懒加载用了() => import(...),这是Vue Router推荐的处理方式。首屏只加载登录页需要的代码,进入考勤页面时才加载对应组件,这样打包后的chunk会小很多,首屏加载速度有明显提升。

3.2 考勤打卡页面的核心交互实现

现在看考勤系统最核心的页面:打卡页面。学生登录后进入个人考勤页面,看到一个大按钮“签到”。点击后前端需要做什么?不只是发一个POST请求那么简单。

正常的一套流程是:

  1. 先通过navigator.geolocation获取定位(如果系统需要位置打卡)。
  2. 获取当前时间,与后端配置的课程时间进行比对,判断是否迟到。
  3. 将打卡信息组装成请求参数,POST到后端接口。
  4. 如果打卡成功,前端更新打卡状态,显示“今日已打卡”。防止重复提交。

Vue3组合式API的实现思路:

// attendance.js 组合式函数 export function useAttendance(courseId) { const loading = ref(false); const todayStatus = ref(''); async function checkIn() { if (loading.value) return; loading.value = true; try { const res = await AttendanceApi.checkIn(courseId); todayStatus.value = res.data.status; ElMessage.success('打卡成功'); } catch (error) { ElMessage.error(error.message || '打卡失败'); } finally { loading.value = false; } } onMounted(async () => { const res = await AttendanceApi.todayStatus(courseId); todayStatus.value = res.data; }); return { loading, todayStatus, checkIn }; }

组合式函数的好处是页面组件里只需要引用useAttendance,代码复用性高。如果以后要做“多门课程打卡”,只需要在循环中调用这个函数就行。这是Vue3相比Vue2最直观的优势。

考勤记录查询页面用到了Vue3的另一个常用特性:computed与watch。比如根据时间段过滤记录,我们可以把时间范围变量定义成ref,然后通过computed对考勤列表进行前端过滤:

const startDate = ref(''); const endDate = ref(''); const filteredRecords = computed(() => { if (!startDate.value && !endDate.value) return records.value; return records.value.filter(item => { const date = item.checkInTime.substring(0, 10); return (!startDate.value || date >= startDate.value) && (!endDate.value || date <= endDate.value); }); });

这里有个小坑:后端返回的时间格式如果是2025-01-15T10:23:45,直接在浏览器里展示会不好看,通常用dayjs库格式化。前端项目里引入dayjs后,一行代码就能处理:

import dayjs from 'dayjs'; formattedTime = dayjs(item.checkInTime).format('YYYY-MM-DD HH:mm:ss');

3.3 前端状态管理与接口请求封装

那么多页面,学生信息、打卡状态、用户Token都要共享,Vuex在这个项目里显得有些笨重,源码采用Pinia状态管理是明智的。Pinia的好处在于更精简的API,去掉了mutations,直接在store里定义state和actions:

// stores/user.js export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userName: '', role: '' }), actions: { setLoginInfo(data) { this.token = data.token; this.userName = data.userName; this.role = data.role; localStorage.setItem('token', data.token); }, logout() { this.token = ''; this.userName = ''; this.role = ''; localStorage.removeItem('token'); } } });

配套的axios封装需要做两件事:请求拦截器自动附加Token,响应拦截器统一处理错误码。实际开发中后端返回的统一格式一般是:

{ "code": 200, "message": "success", "data": {...} }

所以前端可以针对code做统一判断,比如code为401时自动跳转登录页:

service.interceptors.response.use( (response) => { const res = response.data; if (res.code === 200) return res; if (res.code === 401) { ElMessage.error('登录已过期,请重新登录'); router.push('/login'); return Promise.reject(new Error(res.message)); } return Promise.reject(new Error(res.message)); }, (error) => { return Promise.reject(error); } );

这种封装对后端开发者很友好,前端只需要调用api.js中定义的方法,不用在每个页面里重复处理错误。

4. 数据库设计与MySQL8.0落地实践

4.1 表结构设计与关系梳理

考勤系统的数据库设计不需要太花哨,但表之间的关系必须理清。我根据源码整理出核心表结构,大致如下:

  • sys_user:用户表,包含用户名、密码、角色、状态。
  • student:学生信息表,包含学号、姓名、班级、专业。
  • course:课程表,课程名称、授课老师、上课时间。
  • teacher:教师表,关联sys_user。
  • attendance_record:考勤记录表,关联student和course,记录打卡时间、状态。
  • leave_request:请假申请表,关联student,记录请假起止时间、证明、审批状态。
  • course_student:课程和学生多对多关联表,因为一个学生选多门课,一门课有多个学生。

这些表之间通过外键逻辑关联,但在实际建表时,使用逻辑外键而不是物理外键是更常见的做法。物理外键有性能损耗,而且后期拆库迁移非常麻烦。很多生产环境都会去掉物理外键,只保留索引和业务层面的约束。这套源码大概率也是这么设计的。

核心考勤记录表的设计要注意索引。由于它承担高频查询和写入,尤其要建立联合索引:

CREATE INDEX idx_course_time ON attendance_record(course_id, check_in_time); CREATE INDEX idx_student_time ON attendance_record(student_id, check_in_time);

有了这两个索引,“按课程查某天考勤”“按学生查历史记录”这样的查询会快很多。虽然数据量少的时候感觉不明显,但一旦数据量到几十万条,没索引和有索引的差距是数量级的。

请假申请表的状态字段建议用int值,比如0待审批、1通过、2驳回,而不用字符串。虽然点击状态可读性差一点,但数据库中数字存储更省空间,查询更快,后端用枚举或常量类映射就行。

4.2 MySQL8.0安装与初始化脚本

拿到源码后第一步是导入数据库脚本。常见的情况是源码包里附带一个attendance_system.sql文件。导入方法很直接,先在MySQL里建库,再导入数据:

mysql -u root -p create database attendance_system default character set utf8mb4 collate utf8mb4_general_ci; use attendance_system; source /path/to/attendance_system.sql;

MySQL8.0默认字符集是utf8mb4,但为了兼容性和避免乱码,建库时最好还是显式指定utf8mb4。utf8mb4_general_ci是排序规则,对中文和英文的兼容性都很好。

导入脚本后,建议先查一下核心表的数据是否有默认账号。一般源码会内置一个管理员账号,比如admin/admin123,一个老师账号,一个学生账号。这些账号的密码在数据库中通常以MD5或BCrypt加密存储。如果你的前端登录不成功,可能是加密方式对不上,需要看后端的密码加密工具类是用MD5还是BCrypt。

MySQL8.0有个和旧版不同的地方:身份认证插件默认是caching_sha2_password。很多老版本的数据库连接工具(比如Navicat 15以下)连接8.0可能会报错,所以需要修改用户认证方式:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

虽然5.7时代推荐mysql_native_password,但8.0默认caching_sha2,导致部分旧客户端不兼容。这个问题很多人都会踩,建议把连接驱动和工具都升级到支持8.0的版本,而不是修改认证方式,因为caching_sha2_password更安全。

4.3 统计类SQL的写法优化

考勤统计是这类系统的重头戏。老师最常看的报表是“学生出勤率”“迟到次数”“请假天数”。如果用Java代码循环统计,效率低代码又啰嗦。MySQL8.0支持窗口函数,能让这类统计查询清晰很多。

比如统计某门课程每个学生的出勤次数排名:

SELECT student_name, course_name, COUNT(*) AS attendance_count, RANK() OVER (ORDER BY COUNT(*) DESC) AS rank_no FROM attendance_record r JOIN student s ON r.student_id = s.id JOIN course c ON r.course_id = c.id WHERE r.status = '正常' GROUP BY student_name, course_name;

这里的RANK() OVER比在Java里做排序再赋值排名高效得多。如果你拿到的源码中没有这种统计SQL,建议自己加上。因为考勤系统的亮点除了打卡功能外,就是统计起来很直观,这能直接提升项目的完成度。

5. 部署运行全流程与文档使用指南

5.1 本地运行环境准备

这源码的说明文档如果把依赖工具的版本写清楚,能省去很多麻烦。我的建议是统一使用以下版本:

工具推荐版本备注
JDK1.8或11SpringBoot2支持
Maven3.6+用于后端依赖管理
Node.js16.x或18.xVue3 + Vite的构建要求
MySQL8.0.x数据库存储
IDEA2023+或任何支持Maven的IDE
VSCode最新用于前端开发

后端启动前需要在IDEA里设置Maven仓库路径。如果下载依赖很慢,建议配置阿里云Maven镜像,在settings.xml的mirrors节点添加阿里云镜像。前端依赖安装用npm默认源会很慢,使用淘宝镜像可以提速:

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

5.2 前后端分离运行步骤

后端启动步骤:

  1. 导入attendance_system.sql到MySQL。
  2. 修改application.yml中的数据库账号密码。
  3. 修改pom.xml里是否已经引入spring-boot-maven-plugin,用Maven打包或直接运行主类。
  4. 启动后访问http://localhost:8080/api/doc.html(如果集成了Knife4j)或http://localhost:8080/swagger-ui.html看接口文档。

前端启动步骤:

  1. 在终端进入frontend或web目录。
  2. 执行npm install安装依赖。
  3. 执行npm run dev启动Vite开发服务器。
  4. 默认地址是http://localhost:5173。

开发时会有个前后端联调问题。前端请求的接口地址默认是http://localhost:8080,但跨域策略会拦截请求。解决办法有两种:一是后端配置跨域过滤器,二是前端Vite配置代理。搭建最简单的还是Vite代理,在vite.config.js里:

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

这样前端请求/api/login,实际代理到后端http://localhost:8080/api/login。这个配置建议第一时间检查源码里是否已写好。

5.3 文档中包含的部署注意事项

这套源码附带文档很详细,至少包含数据库初始化、接口文档、部署说明。但文档里有几个容易被忽略的细节要格外注意。

一个是application.yml里的文件上传路径和虚拟路径映射。考勤系统如果支持学生上传附件(比如请假证明),那么spring.servlet.multipart.max-file-size要配置成合理值,比如10MB。同时上传后文件的存储路径需要提前创建,否则启动时虽然不报错,但上传操作会失败。

另一个是日志文件路径。源码如果配置了logback,logback.xml里log.path如果指向一个不存在的目录,启动可能不会报错,但日志不会输出。建议把日志路径设置为./logs,并在项目根目录下创建logs文件夹。

最后是打包部署环节。前后端分离的最终产物是可以放在同一台服务器上的。执行mvn clean package得到后端jar包,执行npm run build得到前端dist目录。使用Nginx托管dist目录,然后把/api请求反向代理到后端端口,这样项目就能以一个统一域名的形式上线。

6. 常见问题与排查技巧实录

6.1 高频环境与启动问题速查表

把我在搭建和运行这套系统时遇到的典型问题整理成速查表,按出现频率排序:

问题现象可能原因排查与解决
连接数据库报“Public Key Retrieval is not allowed”MySQL8.0驱动要求allowPublicKeyRetrieval=true在jdbc url加allowPublicKeyRetrieval=true
连接数据库报时区错误serverTimezone未设置或时区不对在jdbc url加serverTimezone=Asia/Shanghai
后端启动端口被占用8080端口被占用改application.yml的server.port,或杀掉占用进程
MyBatis-Plus分页无效,返回全部数据未注册分页插件创建MybatisPlusInterceptor,并添加PaginationInnerInterceptor
前端npm install报错ERESOLVE依赖版本冲突或Node版本过高用nvm切换到Node16.x,或删除package-lock.json重新install
前端请求接口404代理未配置或后端路径不对检查vite.config.js的proxy配置,检查后端路由前缀
登录提示“密码错误”但账号没问题数据库密码加密方式不对查看密码工具类,确认是MD5还是BCrypt,插入对应加密值
上传文件失败存储路径不存在或大小超限创建目录,在配置中调大max-file-size
考勤时间比本地时间慢8小时数据库时区或JVM时区不一致MySQL连接参数加serverTimezone=Asia/Shanghai,确认系统时区为CST
页面样式错乱,组件渲染不出来Vue3组件引入方式错误检查是否用了app.use()引入Element Plus,或组件是按需导入导致漏掉样式

6.2 独特踩坑经验:MyBatis-Plus的字段自动填充

考勤记录表通常需要记录创建时间和更新时间。物理表设计里如果定义了create_time和update_time字段,那么MyBatis-Plus里有两种维护方式。一是每次插入时手动set,二是利用MyBatis-Plus的元数据字段填充功能。

源码里如果用了@TableField(fill = FieldFill.INSERT)注解,那么需要在配置类里实现MetaObjectHandler接口:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

如果没有这个处理器,插入数据时会发现create_time是NULL,虽然不报错,但统计时按时间排序就会出问题。这个坑很隐蔽,不仔细看数据表不会发现。

6.3 前端Vue3常见的响应式陷阱

Vue3的响应式系统基于Proxy,使用方式得当会非常顺手,但如果拿Vue2的习惯去写就会踩坑。在考勤系统里最典型的问题是对数组索引的修改。比如:

records[index].status = '迟到'; // 这个操作在Vue3中能触发响应式

Vue3的Proxy让这样的修改也是响应式的,不需要像Vue2那样用splice或$set。但反过来,如果用reactive创建对象,再对对象直接赋值整个结构:

const state = reactive({ list: [] }); state.list = fetchedData; // 这个操作在Vue3中可以

但在Vue2中这样写会丢失响应式。还有一点是ref在模板中使用会自动解包,但在JavaScript中要记住加.value。很多初学者在script里写records.value,在模板里写records,这没问题。但如果在script里写records.length,就会拿到undefined,因为ref对象里不会直接暴露length属性。

前端调试技巧方面,Vue3 DevTools是必备工具。在浏览器扩展商店里安装Vue.js Devtools,能直观看到组件的setup状态、当前计算属性值、pinia store内容。定位考勤页面“打卡按钮状态不对”的问题时,这个工具比console.log高效得多。

7. 项目二次开发与个人扩展体会

拿到一套能跑的源码只是开始,真正能学到东西的是“拆开它、改造它、完善它”。对大学生考勤系统来说,有几个非常值得扩展的方向。

一个是引入消息通知机制。比如学生提交请假申请后,老师端能实时收到提醒。这类功能用WebSocket实现不算难,SpringBoot2里用spring-boot-starter-websocket,前端维护一个WebSocket连接,老师端就能收到审批提醒。源码里即使没写,也可以在现有基础上加。

另一个是导出功能。老师查看考勤统计结果后,往往需要导出Excel。Java后端常用EasyExcel或POI,前端则可以直接调用window.open访问导出接口,或者用Blob下载。如果系统后续要对接教务系统,导出的考勤数据格式规范就显得很重要。

数据库层面的扩展,可以考虑增加“考勤规则表”,把每门课程的允许迟到时间、签到时间段、是否允许补签等规则独立成表,比现在直接把规则写死在代码里灵活得多。这也是我最近在做的事情:将课程签到逻辑抽成规则引擎,每个老师都能自定义自己课程的考勤规则。

最后再说一下学习和复现这套源码的建议。第一遍先跑起来,把登录、打卡、审批、统计这些主流程走通。第二遍看代码,重点看Controller、Service、Mapper之间的调用关系,理解MyBatis-Plus的通用CRUD和LambdaQueryWrapper的写法。第三遍尝试改造,比如把前端页面换成自己的UI风格,或者增加一个“按周视图”的考勤日历,改造过程中遇到的问题才真正是提升能力的机会。

写代码和做系统,最怕的不是不会写,而是不知道有坑。这套考勤系统虽然不算大,但前后端交互链路完整,数据库设计规范,文档齐全,拿来做毕设启动项目或者学习参考都非常合适。我实际搭建和测试过程中踩过的那些时区、分页、跨域、加密方式、字段填充的坑,都总结在前面了。如果你在跑源码时遇到卡壳,不妨对照排查。记住一点,遇到报错不要急着改代码,先看日志,再看配置,最后看版本和依赖关系,绝大多数问题都能靠耐心定位解决。

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

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

立即咨询