ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

考勤系统开发实战:SpringBoot2+Vue3完整路线与踩坑记录

考勤系统开发实战:SpringBoot2+Vue3完整路线与踩坑记录 毕业设计做过考勤系统的都知道这套东西看着简单真做起来全是坑。时间同步、状态流转、跨域联调、分页失效随便一个都能卡你两三天。最近正好有位读者发来一个基于 SpringBoot2 Vue3 的考勤系统源码还带着全套文档我把整套思路和实操过程完整跑了一遍今天把我自己的摸索过程、踩坑记录、优化建议一次性写清楚给正在做类似设计的同学提供一个可以直接参考的路线。1. 项目概述大学生考勤系统到底在解决什么问题1.1 核心需求拆解这个项目是典型的 Java Web 管理系统目标用户是高校的教务管理人员和任课教师。和网上泛滥的图书管理系统商城系统不一样考勤系统有一个非常突出的特点——它是强规则、强状态的业务系统不是简单的增删改查。所谓强规则指的是考勤状态不是随便填的。正常出勤、迟到、早退、旷课、请假这些状态之间有严格的业务逻辑请假要审批审批通过后才能把日期段标记为请假状态同时冲突的课程记录要联动更新。这些逻辑如果不在代码层面处理好最终统计出来的出勤率就是错的。从技术栈来看这个项目定位非常明确后端SpringBoot2 MyBatis-PlusJava 8 环境前端Vue3 Vue Router Pinia或 Vuex配合 Element Plus 组件库数据库MySQL 8.0存储引擎 InnoDB字符集 utf8mb4整个系统的边界清晰功能基本覆盖了传统考勤的核心闭环教师创建课程 → 学生选课 → 每节课点名签到 → 请假申请审批 → 月度/学期出勤率统计。同时它天然附带三类角色——管理员、教师、学生所以权限管理也顺理成章成为项目的一部分。1.2 这套系统适合谁怎么用最划算如果你是正在准备毕业设计或者课程设计的在校生这个项目可以作为一份覆盖面很广的模板来参考。原因有二第一技术组合非常贴近目前企业主流。SpringBoot2 和 Vue3 的搭配既能体现后端分层架构思想也能展示前后端分离的开发模式。MyBatis-Plus 虽然帮你省了很多重复的 SQL 操作但底层依然是 MyBatis面试被问到 SQL 优化和 ORM 原理时不会被问住。第二文档齐全意味着你可以把更多精力放在理解而不是猜想上。项目文档一般会包含需求分析、数据库设计、核心接口说明、部署手册这些材料本身也是毕业论文的重要支撑素材。建议拿到源码后不要急着跑起来先花半小时过一遍目录结构和 SQL 脚本搞清楚表与表之间的关系再动手启动项目。2. 技术选型为什么每一环都是一张稳妥牌2.1 后端SpringBoot2 为什么至今还是毕业设计的主力很多同学看到 SpringBoot3 已经发布就会纠结要不要用新版。我的建议是除非你有必须用新特性的硬需求否则 SpringBoot2 依然是论文型项目的安全选择。原因很实在。SpringBoot2 借助自动配置和 starter 机制几行代码就能组装一个可运行的 Web 服务学习曲线比早期 SSM 不知道平滑了多少。同时它对 JDK8 的兼容非常成熟大部分云服务器、学校机房、自己的本地电脑只要装了 JDK8 就能跑不需要额外折腾 JDK17 的环境变量和模块化问题。更重要的是生态。SpringBoot2 的教程、社区解答、第三方 starter 数量极其庞大。你做考勤系统时一旦遇到问题——比如拦截器配置、全局异常处理、文件上传搜出来的解决方案十有八九都是基于 SpringBoot2 写的参考成本低。而 SpringBoot3 的很多用法虽然一脉相承但细节上比如 javax 包改为 jakarta会让你在查资料时不断做脑内翻译对新手很不友好。考勤系统在这个框架下的核心职责非常清晰用 Spring MVC 的 Controller 层接收前端请求统一返回 JSON 格式数据用 Service 层处理业务规则比如请假审批后自动更新考勤状态用 MyBatis-Plus 的 Mapper 层做数据持久化复杂统计用 XML 自定义 SQL用 Spring Security 或 JWT 拦截器做登录认证和角色鉴权另外SpringBoot2 的日志体系SLF4J Logback默认就给你配好了控制台输出、文件输出都开箱即用这在调试考勤状态流转时帮了大忙。你完全可以靠日志把一条数据从教师发起签到到学生提交签到再教师确认状态的全过程跟踪出来。2.2 前端Vue3 组合式 API 带来的开发效率提升Vue3 发布这么久新项目选它不需要太多犹豫。但是要注意Vue3 和 Vue2 不是简简单单的升级很多写法习惯要改。考勤系统的前端页面多逻辑相似度较高如果用 Vue3 的组合式 API可以把创建课程表单和创建考勤记录表单的公共逻辑抽取成可复用的 hook 函数代码复用效率明显提升。这个项目中前端核心负责的事情有这么几块登录页和角色路由守卫未登录跳转登录页教师访问不了学生统计页课程管理页课程的创建、编辑、选课学生列表考勤签到页教师发起签到学生提交状态倒计时提醒请假申请页学生提交请假、教师审批统计展示页出勤率可视化通常配 ECharts 柱状图/饼图Vue3 配套的 UI 组件库 Element Plus 在表单验证、表格操作、日期选择器这些高频使用场景里非常顺手。比如请假日期段选择只需要给el-date-picker设置typedaterange配合 rules 校验规则就能把前后端的时间范围校验一起做了。必须提醒的是IPv6 或代理环境下 Vue3 的 devServer 很容易出现白屏。如果你在浏览器打开前端页面发现空白且控制台报ERR_ADDRESS_INVALID大概率是 devServer 默认绑定了 localhost 的 IPv6 解析出了问题。解决办法是在vite.config.ts里显式设置server.host 127.0.0.1或0.0.0.0然后记得重启前端服务。2.3 ORMMyBatis-Plus 为什么是这种项目的捷径如果不用 MyBatis-Plus原生 MyBatis 的 Mapper 文件里就要写大量重复的insert into、select * where id ?这类模板 SQL。对于考勤系统这种实体多、字段多的项目手写这些 SQL 纯属浪费生命。MyBatis-Plus 的BaseMapper接口直接给你提供了selectById、selectList、insert、updateById、deleteById等通用方法单表 CRUD 几乎不需要写 SQL。它在这个系统里最亮眼的功能有三个第一分页插件。考勤记录会随着课程和学期累加一个学期下来可能上万条数据。使用PaginationInnerInterceptor配合PageT对象一行代码就能完成分页查询连 count 自动生成都帮你处理好了。第二条件构造器QueryWrapper/LambdaQueryWrapper。查询某位学生的旷课记录可以这样写LambdaQueryWrapperAttendance wrapper new LambdaQueryWrapper(); wrapper.eq(Attendance::getStudentId, studentId) .eq(Attendance::getStatus, 0) .orderByDesc(Attendance::getCourseDate); ListAttendance list attendanceMapper.selectList(wrapper);这种写法在考勤条件筛选、期末统计出勤率时非常直观而且 Lambda 表达式的写法是编译期检查不会因为字段名手写错误跑出莫名的 SQL 异常。第三逻辑删除。考勤记录和请假记录往往是教学档案的一部分虽然实际需求里删除操作很少但一旦教师误操作删除了一条考勤记录直接物理删除会导致统计结果出现不可追踪的缺口。MyBatis-Plus 的TableLogic注解可以轻松实现逻辑删除配置好logic-delete-value和logic-not-delete-value后所有delete操作都会自动变成update语句数据安全性和可追溯性都兼顾了。2.4 数据库MySQL8.0 的升级价值不容忽视MySQL 从 5.7 升级到 8.0对开发者最直观的变化有几个默认字符集已经是utf8mb4emoji 表情和生僻字不会再出现乱码支持窗口函数复杂的统计 SQL 可以写得简洁WITH语句CTE让递归查询更顺手JSON类型函数进一步增强如果考勤系统里要存每个学生的签到坐标、签到图片地址用 JSON 字段可以避免额外建表。但 8.0 也带来了一些升级副作用做项目的时候必须提前规避驱动类变了com.mysql.jdbc.Driver已经废弃必须改成com.mysql.cj.jdbc.Driver时区问题连接 URL 上必须加serverTimezoneAsia/Shanghai不然启动直接报时区错误认证插件变更MySQL8.0 默认使用caching_sha2_passwordJDBC 连接时要注意驱动版本不能太老最好用mysql-connector-java8.0.x 以上对应到application.yml标准配置长这样spring: datasource: url: jdbc:mysql://localhost:3306/attendance_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue这行是我后来加上的不然 MySQL8.0 在某些连接场景下会报Public Key Retrieval is not allowed的错。这种小坑在文档里一般不写实际用起来才发现缺一不可。3. 核心功能模块与数据库设计拆解3.1 角色权限模型三种角色一种思维大学生考勤系统按照业务角色可以拆成三类用户系统管理员、任课教师、学生。不要小看角色区分很多毕设项目做得很糙就是一张用户表加一个 role 字段然后前端根据 role 来隐藏按钮后端接口完全不鉴权这不叫权限设计只叫界面伪装。正确的做法是引入角色-权限体系。常见的实现方案有两种方案ASpring Security JWT通过注解PreAuthorize(hasRole(TEACHER))控制接口权限方案B自定义拦截器 Redis 存储登录态在拦截器里校验角色考勤系统用方案A会更正规一些但配置稍微复杂。你需要定义一个UserDetailsService、一个JwtAuthenticationFilter还要写SecurityConfig放行登录接口和静态资源。如果工期紧张也可以采用简化方案自研 HandlerInterceptor配合一个自定义注解RequireRole(teacher)在拦截器里解析 JWT 并校验角色逻辑直观且容易在答辩时讲清楚。角色权限模型还要处理好数据范围的问题。同是教师A 老师只能看到自己教的那几门课的考勤记录同是学生只能看到自己的出勤情况。这个数据隔断如果忘做了学生拿学生账号调用getAllAttendance接口就直接把自己的隐私和全班人共享了。建议在 Service 层强制传入当前登录用户上下文SQL 层使用 MyBatis-Plus 的Intercepts或者简单地在 Wrapper 里加eq(user_id, 当前用户ID)才能保证数据不越权。3.2 核心表结构设计与索引优化根据多年做管理系统的经验考勤系统的表不需要设计得特别复杂但表与表之间的关联必须干净。我建议至少包含下面这些核心表表名用途关键字段sys_user用户表id, username, password, real_name, role, student_no, class_idsys_role角色表id, role_code, role_namecourse课程表id, course_name, teacher_id, course_code, semester, class_idcourse_student选课关系表id, course_id, student_idattend_record考勤记录表id, course_id, student_id, course_date, status, remarkleave_request请假申请表id, student_id, start_date, end_date, reason, status, approver_idstatistics统计汇总表可选id, student_id, course_id, total_count, present_count, absent_count你别看表少这里面有几个设计细节值得重点琢磨第一考勤记录表要加course_date字段而不是用create_time代替。因为考勤是按上课日期统计的而记录创建时间可能因为补签、导入等情况和实际上课时间不一致。如果统计出勤率用了创建时间数据会非常混乱。第二请假记录表和考勤记录表要引用一致。请假通过后学生的考勤状态应该是请假而不是缺勤。这需要事务控制审批请假通过时同步把请假时间段内的考勤记录批量更新为请假状态。不这么处理的话统计缺勤次数时会出现明明请了假还显示旷课的尴尬。第三联合索引要建立在查询高频字段上。典型的查询是查某门课某一天的所有学生考勤情况或者查某学生在某学期所有课程的出勤记录。对应的索引设计ALTER TABLE attend_record ADD INDEX idx_course_date (course_id, course_date); ALTER TABLE attend_record ADD INDEX idx_student_status (student_id, status); ALTER TABLE leave_request ADD INDEX idx_student_date (student_id, start_date, end_date);添加索引的核心原则是查询条件里出现频率最高的字段放在最前面。不要每个字段都建索引写入性能会崩更不要不建索引数据量几张表还好student 上千、record 过万后慢查询会明显拖累页面响应。3.3 状态机设计考勤和请假的状态流转上面一句审批请假通过时同步更新考勤状态听着轻巧实现起来其实就是状态机的问题。我强烈建议在设计表结构的同时把所有状态迁移图画清楚哪怕只是在纸上画。考勤状态至少应该有四种出勤、迟到、缺勤、请假。教师点名后首次记录可能是出勤或迟到如果某学生提交了请假申请且被通过该生的考勤状态就应该是请假。这里要注意的不是状态本身而是状态之间的变换规则缺勤不能被直接改成出勤必须有补签操作和备注请假状态只能由请假审批流程触发不能手工把考勤记录改成请假已结束的课程course_date 小于当前日期默认锁定不允许再修改考勤用 Java 枚举来管理状态比用魔法值靠谱得多public enum AttendanceStatus { PRESENT(1, 出勤), LATE(2, 迟到), ABSENT(3, 缺勤), LEAVE(4, 请假); private final int code; private final String desc; // getter / setter }枚举类型让参数可读性大幅提升在 MyBatis-Plus 里配合EnumValue注解可以把枚举直接映射到数据库的 int 字段前后端交互也只需要传 code减轻 JSON 体积和沟通成本。请假的状态则建议待审批 → 通过 / 拒绝。这就是一个简单的二态流转但要注意拒绝后原考勤记录不能动继续保持缺勤或未处理的状态通过后要开启一个延时任务或事务回调把时间段内所有相关考勤记录状态刷成请假。4. 实操落地从零跑通这个项目的全过程4.1 环境准备与版本搭配拿到源码之后第一件事不是急着 IDEA 打开而是把环境核对清楚。项目既然指定了 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0我就按这套组合来准备JDK 81.8.0_202 以上Maven 3.6.3 或以上Node.js 14 LTS 或以上Vue3 项目建议不超出 16避免版本兼容问题MySQL 8.0 实例Win 下用 MySQL InstallerMac 下用 Homebrew 或 Docker 都行IDEA 2021.3前端建议配好 Volar 插件而不是 VeturVetur 对 Vue3 支持不完整我自己的环境是 Windows MySQL8.0 解压版 IDEA Chrome调试顺利。MySQL 8.0 安装这一步会拦住不少人。如果你是 Windows最容易的路径是下载 MySQL Installer选择 Server Only用Use Strong Password Encryption初始化 root 密码。安装完记得确认一下端口是 3306 且没有被占用。如果你更偏好 Dockerdocker run --name mysql8 -e MYSQL_ROOT_PASSWORD123456 -p 3306:3306 -d mysql:8.0启动后用docker exec -it mysql8 mysql -uroot -p进入容器执行源码里的init.sql即可。这里有个容易忽略的点MySQL8.0 默认使用caching_sha2_password认证如果你用了一些旧版可视化工具比如特别老的 Navicat可能连不上。解决办法要么升级客户端要么在建用户时指定mysql_native_password。我建议直接换最新版本客户端省得后续出现客户端能连Java 驱动连不上的诡异问题。4.2 后端启动的 5 个关键步骤后端部分以 SpringBoot2 为主启动流程本身不复杂但顺序很重要。第一步导入数据库。用 source 命令或脚本执行init.sql确保所有表创建成功然后插入初始管理员账号通常是 admin/admin123。如果导入时遇到字符集问题检查 SQL 文件头部是否声明了SET NAMES utf8mb4。第二步修改数据库连接配置。打开src/main/resources/application.yml把 url、username、password 改成你自己的。注意端口别冲突默认 8080 如果被占用改成 8081 或者 9090。第三步确认 Maven 依赖拉取完整。在项目根目录执行mvn clean install -DskipTests看到 BUILD SUCCESS 再继续。如果导入 IDEA 后一直有红叉多半是 Maven 仓库镜像问题检查 settings.xml 里的阿里云镜像配置。第四步运行启动类。找到带SpringBootApplication注解的类右键 Run。看到 Spring Boot 的 banner 和Tomcat started on port(s): 8080日志说明后端起来了。第五步验证接口。浏览器直接访问http://localhost:8080/api/login或项目的健康检查接口看是否返回 JSON。如果控制台报 404多半是上下文路径或接口前缀问题查看application.yml中是否配置了server.servlet.context-path。后端启动阶段最典型的坑是 MyBatis-Plus 找不到 Mapper。如果启动时日志报Invalid bound statement (not found)检查启动类上是否加了MapperScan(com.example.mapper)注意包路径要和你实际的 Mapper 接口所在包一致。4.3 前端启动与跨域处理后端跑通了再看前端。Vue3 项目启动命令很固定npm install npm run devnpm install下载依赖时如果速度奇慢切换淘宝镜像源npm config set registry https://registry.npmmirror.com依赖装完后Vite 默认端口是 5173打开http://localhost:5173就能看到页面。但这时候大概率会遇到跨域问题——前端页面在 5173 端口后端接口在 8080 端口浏览器的同源策略会拦下所有请求。解决办法是前端配置代理。在项目根目录vite.config.ts中import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { host: 127.0.0.1, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 如果你的后端接口没有 /api 前缀可能需要 rewrite rewrite: path path.replace(/^\/api/, ) } } } })这里有一点要特别说明proxy的路径匹配规则取决于前端封装的 axios 请求地址是什么。如果前端所有请求都写成了/api/login而后端实际接口是/login那需要 rewrite 去掉/api如果后端本来就有/api上下文那直接透传就行不要强行 rewrite。很多同学白屏或 404就是这里配置逻辑理解反了。刚接触 Vue3 的话推荐把网络请求统一封装到一个request.js/http.ts文件里设置 baseURL 为/api并加上请求拦截器和响应拦截器。拦截器统一处理 token 注入、401 跳转登录、后端业务码判断能少写非常多的重复代码。4.4 打包部署要点项目开发完成后如果要部署到服务器或给学生演示建议熟练使用打包命令。后端打包mvn clean package -DskipTests打完的 jar 包在target/目录下直接用java -jar xxx.jar启动即可。生产环境记得配置外部化的application-prod.yml把数据库地址和密码单独管理不要把密码硬编码在代码里。前端打包npm run build打包产物在dist/目录。部署方案有两种方案A把dist里的文件扔进 Nginx配置反向代理到后端 Jar 端口方案B把dist里的文件放到后端项目的src/main/resources/static/重新打包 Jar后端直接托管静态资源方案B适合学生演示环境简单快速但要注意 Vue3 如果用了createWebHistory()路由模式刷新页面会 404需要在后端加一个资源回退的 Controller 转发或者改用哈希模式。我个人偏向方案A前后端分离部署更符合企业工作流也方便以后扩展移动端。5. 踩坑实录最常见的问题与排查办法5.1 启动期问题速查表无论是自己写的代码还是拿到的源码启动阶段最容易遇到下面几个问题我按出现频率从高到低整理成一张表现象根本原因解决办法启动报The server time zone value Öйú±ê׼ʱ¼äMySQL8.0 连接 URL 缺少时区参数URL 添加serverTimezoneAsia/Shanghai启动报Driver com.mysql.cj.jdbc.Driver找不到mysql-connector-java 版本过低pom 中改用 8.0.31 以上版本启动报Failed to configure a DataSourceapplication.yml 加载失败或数据库连不上检查用户名密码、端口、库名是否存在启动后访问接口 404上下文路径/拦截器配置不匹配检查server.servlet.context-path与请求前缀页面能打开但接口全报401JWT 拦截器拦截了登录接口在拦截器放行/login、/captcha、静态资源查询列表分页不生效未注册 MyBatis-Plus 分页插件新建MybatisPlusConfig注册PaginationInnerInterceptor第 5 条非常隐蔽。自研 token 拦截器时大家习惯拦截/api/**全部路径结果把所有登录和注册请求也拦截了。我的做法是拦截器里显式配置放行列表并且用excludePathPatterns方式维护一份明确的清单避免后面新增公开接口时遗忘。5.2 联调期问题速查表前后端联调阶段问题更多元化但大多数是有规律可寻的现象根本原因解决办法前端请求报CORS error未配置跨域或代理失效优先检查 vue.config / vite.config 代理是否生效请求能到达后端但拿不到登录用户Token 没有从 LocalStorage 取出来放进 Headeraxios 请求拦截器统一加Authorization日期数据差 8 小时Jackson 序列化时间默认 UTCapplication.yml 配置spring.jackson.time-zone: GMT8表格数据为null但数据库有值实体类字段名和数据库列名映射不上确认驼峰映射配置或使用TableField注解删除一条记录后数据消失未开启逻辑删除实体类字段加TableLogic配置逻辑删除值统计报表数据翻倍多表连接时没有去重检查 left join 是否产生笛卡尔积使用 distinct其中日期 8 小时偏差在考勤系统里最致命。试想学生上午 8 点的课程前端显示成了凌晨 0 点整个签到时间全都错乱了。解决思路有两个层面数据源层面保证 MySQL 连接带serverTimezoneAsia/ShanghaiJackson 序列化层面设置本地时区。两者都做了才稳。5.3 我的个人经验补遗这部分是我实操中总结的不属于标准文档但绝对实用。第一需求文档和代码要做交叉验证。拿到项目源码后不要只看 README要打开文档里的需求分析章节核对每个功能是否真正落地。比如需求里写教师可以自定义考勤二维码如果代码里只是生成了随机字符串而没有实际的动态刷新、过期机制那你答辩时要额外准备解释不能直接照搬。第二数据库必须开启 binlog 或至少定时备份。考勤记录是教学数据丢了很麻烦。自己练习时可以写一个简单的 MySQL 事件每周自动导出 SQL 备份。这不复杂但能避免开发测试时手滑DELETE FROM attend_record没加 WHERE 的毁灭性操作。第三前端组件别贪多。很多同学拿到 Vue3 项目就想着封装各种炫酷组件但考勤系统核心价值在于功能稳定、数据准确。把精力花在完善异常处理重复提交、网络超时、Token 过期、优化用户体验加载状态、空数据提示上比硬搬一堆华而不实的图表更值得。第四预留扩展点。如果时间允许建议在代码里预留接口抽象。考勤系统未来常见扩展是接入人脸识别、GPS 定位签到、导出 Excel 报表。在 Service 层定义接口而不是写死实现类后面扩展会轻松很多。6. 结尾一些实际的开发建议我自己把考勤系统做了两个版本迭代之后最大的体会是这类管理系统技术上并不花哨真正难的是把业务流程想清楚并且用代码严谨地表达出来。SpringBoot2 和 Vue3 的这套组合刚好提供了一套成熟稳重的底座让你能把精力放在业务建模和细节验证上而不是折腾框架本身。如果你正在参考这套源码做自己的项目我建议在跑通之后至少尝试改两个地方一是给考勤记录增加报表导出功能结合 POI 或 EasyExcel二是把登录认证从简单的 Token 拦截器升级为 Spring Security JWT。这两个改动代码量不大但能明显提升项目的成熟度面试聊起来也有话说。最后分享一个小技巧开发过程中把每一个接口的请求参数和返回值截图保存。这既是调试的辅助也是论文素材的一部分。我之前写毕业设计系统截图少了最后补起来非常痛苦。你从第一天就注意积累后期写文档就不会手忙脚乱。祝你的考勤系统能顺利跑通顺便通过答辩。
返回列表