news 2026/9/5 17:51:32

SpringBoot+Vue3前后端分离员工考勤管理系统:权限设计与实践详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue3前后端分离员工考勤管理系统:权限设计与实践详解

SpringBoot、Vue3、前后端分离,这三个词放在一起,基本就是当前 Java Web 课程设计和毕设项目里出现频率最高的组合之一了。如果再加上“员工考勤管理系统”,一个典型的后台管理业务闭环就出来了:部门员工管理、考勤打卡、请假审批、统计报表。

这次我们拆解的“068基于SpringBoot+Vue3前后端分离的员工考勤管理系统”,不是那种动辄几十张表、搞一堆中间件的重系统,而是把前后端分离、RBAC 权限、考勤业务、报表统计这些技术点集中在一个完整项目里。对正在做毕设、课程设计,或者想系统练一遍 SpringBoot + Vue3 全栈流程的同学来说,这套项目模板值得花时间过一遍,它能一次性覆盖从数据库设计、后端接口开发、前端页面联调到服务器部署的完整过程。

这篇文章不会去复述那种“xxx系统有什么意义”的空话,而是按实际开发顺序来看:这个系统负责什么、表怎么设计、后端接口怎么分层、Vue3 前端怎么对接、JWT 鉴权怎么做、打包部署要改哪些配置、最容易踩的坑又在哪里。建议收藏备用,下面直接进入正题。

1. 核心能力速览

先把系统形态和技术栈整体列出来。下面的能力表基于当前这套 SpringBoot + Vue3 前后端分离考勤项目的常见实现范围,具体以你本地拿到或自建的源码为准:

能力项说明
系统类型前后端分离的员工考勤管理系统
后端技术栈SpringBoot、Spring Security / JWT、MyBatis-Plus、MySQL
前端技术栈Vue3、Element Plus、Pinia、Vue Router、Axios、Vite
主要功能员工管理、部门管理、考勤打卡、考勤记录、请假审批、排班/报表统计
权限模型基于角色的访问控制,管理员、普通员工等不同角色
启动方式后端 Maven 启动,前端 npm run dev,生产环境分离部署
接口风格RESTful JSON 接口,Axios 统一封装请求
是否支持 API后端天然提供 REST 接口,便于后续扩展小程序或移动端
是否支持批量任务考勤报表导出、月末统计等可按批次处理,需要自行扩展
推荐环境JDK 8/11、Node 16+、Maven 3.x、MySQL 5.7/8.0
适合场景Java 毕设、课程设计、前后端分离入门到进阶训练项目

从模块上看,这个系统最大的价值不在于算法或高并发,而在于业务逻辑完整、权限链路清晰。对初学者来说,只要把它完整跑一遍,SpringBoot 分层架构、MyBatis-Plus 数据操作、Vue3 组件通信、动态路由、Axios 拦截器、JWT 鉴权这些知识点基本都能串起来。

2. 适用场景与使用边界

一个考勤管理系统到底适合谁?先说结论:它最适合作为学习项目、毕设选题和中小型企业内部考勤的二次开发底座。想靠它支撑大型企业几千人的复杂考勤规则,那不合适,硬套只会把自己坑了。

2.1 适合的学习者

刚学完 SpringBoot 基础,但对实际项目怎么分层、Controller 怎么写、Mapper 怎么封装还比较懵的人,可以通过这个项目看到一套标准写法。比如 Service 层接口和实现类分离、统一返回结果、全局异常处理、JWT 登录拦截、Vue3 里页面刷新后动态路由恢复的处理,都是实际项目里常用的工程习惯。

正在准备毕设答辩的人,最关心的是“这个系统有没有业务亮点”。考勤系统的亮点往往集中在数据关联上:请假天数有没有联动考勤状态、加班工时有没有计入月度报表、当月考勤统计能不能按部门展示。只要把这类业务闭环写清楚,答辩基本能站得住。

2.2 不适合的场景

这套技术选型适合中小规模、考勤规则简单的业务。如果需要对接钉钉/企业微信打卡、设备指纹防作弊、复杂排班算法、大数据量人脸识别,它就不够用了。更稳的做法是把它当后端管理平台,把打卡端换成钉钉小程序或企业微信 API,数据通过接口同步过来,管理后台仍然使用这套 SpringBoot + Vue3 来实现。

2.3 边界与合规提醒

考勤数据属于员工个人信息。系统里包含姓名、工号、手机号、打卡时间、打卡地点等敏感数据。开发阶段要使用脱敏的模拟数据,不要直接拿真实员工信息来测试;部署上线前要确认公司内部已获得员工授权,并做好账号权限控制与日志审计。文章里涉及的所有接口地址、表结构,都只用于学习环境验证。

3. 系统功能模块与数据模型设计

考勤系统表面上是“打卡”两个字,实际落到功能拆分上,至少要分成四个大模块:系统管理、员工档案、考勤业务、审批与报表。下面是一个通用可参考的模块图,不需要按死框架,但核心模块基本一致。

3.1 功能模块划分

  • 登录与权限:验证码登录、JWT 令牌、角色权限控制、菜单动态加载。
  • 系统管理:用户、角色、菜单管理,通常可以并入部门管理。
  • 员工管理:员工基础信息、部门归属、入职/离职状态、工号唯一。
  • 考勤管理:上班打卡、下班打卡、考勤记录查询、异常补卡申请。
  • 请假管理:请假申请、审批流程(简单场景直接做审批状态流转)、请假天数统计。
  • 报表管理:按部门、个人、时间范围筛选考勤数据,支持导出 Excel。

这里要注意一点:很多毕设项目把考勤打卡简单做成“点击一下按钮,记录当前时间”,这其实不符合真实场景。真实考勤至少要区分上班时间和下班时间,还要考虑迟到、早退、缺卡、请假、加班等状态。做表结构之前,先把这些状态枚举理出来,后面代码才不会写乱。

3.2 核心数据表设计

SpringBoot + Vue3 的考勤系统不需要太多表,常见的核心表可以这样拆分:

表名主要字段作用
sys_userid, username, password, real_name, dept_id, status登录账号与真实姓名
sys_roleid, role_name, role_key角色
sys_user_roleuser_id, role_id用户角色关联
sys_deptid, dept_name, parent_id部门
emp_employeeid, emp_no, name, dept_id, phone, hire_date员工档案
att_attendanceid, user_id, att_date, clock_in_time, clock_out_time, status每日考勤记录
att_leaveid, user_id, leave_type, start_time, end_time, reason, status请假申请

以考勤记录表att_attendance为例,它最核心的字段不是“打了几次卡”,而是att_datestatus。业务逻辑上,一天只应该产生一条考勤主记录,迟到、早退、缺卡都通过clock_in_timeclock_out_time与部门上下班时间比较后计算出来,写进status字段。建表 SQL 大致如下:

CREATE TABLE `att_attendance` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint DEFAULT NULL COMMENT '用户ID', `att_date` date DEFAULT NULL COMMENT '考勤日期', `clock_in_time` datetime DEFAULT NULL COMMENT '上班打卡时间', `clock_out_time` datetime DEFAULT NULL COMMENT '下班打卡时间', `status` varchar(16) DEFAULT NULL COMMENT '正常/迟到/早退/缺卡/请假', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_date` (`user_id`, `att_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤记录表';

user_idatt_date建成唯一索引,可以防止同一个人同一天产生多条考勤记录。在很多考勤系统里,最恶心的 bug 就是用户打了一次卡后刷新页面,又插入了一条记录。唯一索引在数据库层面兜底,比后端if判断更可靠。

3.3 状态机与业务状态

考勤状态不要乱用字符串,建议在 Java 后端定义枚举或常量类。比如:

public class AttendanceStatus { public static final String NORMAL = "NORMAL"; public static final String LATE = "LATE"; public static final String EARLY = "EARLY"; public static final String MISSING = "MISSING"; public static final String LEAVE = "LEAVE"; }

请假记录的状态也类似,可以定义成PENDINGAPPROVEDREJECTED。这里不要做成特别复杂的流程引擎,考勤系统里的请假审批模型通常就是“员工发起 -> 管理员审批 -> 状态更新”,用一张表加一个状态字段就够。引入 Flowable、Activiti 这样的工作流引擎属于过度设计,除非业务上真的需要多人会签、条件分支,否则一个菜鸟项目不会因为“用了 Flowable”而加分,反而会因为流程配置复杂而拖慢进度。

4. SpringBoot 后端接口设计与实现

SpringBoot 项目拿到手以后,先看包结构。一个合格的考勤后端项目,包结构应该是清晰的:

com.example.attendance ├── config // 安全配置、跨域配置、MyBatis-Plus配置 ├── controller // 接口入口 ├── service // 业务逻辑接口 ├── service.impl // 业务逻辑实现 ├── mapper // MyBatis-Plus Mapper 接口 ├── entity // 数据库实体 ├── dto // 请求参数封装 ├── vo // 响应结果封装 ├── common // 统一返回结果、异常处理、常量 └── utils // JWT、日期工具

4.1 后端分层设计

Controller只负责接收参数、校验基础格式、返回结果,不写复杂业务逻辑。比如考勤打卡接口:

@RestController @RequestMapping("/api/attendance") public class AttendanceController { @Resource private AttendanceService attendanceService; @PostMapping("/clockIn") public Result clockIn(@RequestBody ClockInDTO dto) { // 当前登录用户由 JWT 拦截器解析后放入 ThreadLocal Long userId = SecurityUtils.getUserId(); return Result.success(attendanceService.clockIn(userId, dto)); } @GetMapping("/myRecords") public Result myRecords(@RequestParam Integer pageNum, @RequestParam Integer pageSize) { Long userId = SecurityUtils.getUserId(); return Result.success(attendanceService.pageMyRecords(userId, pageNum, pageSize)); } }

这里有一个很容易踩的坑:接口不要把userId从前端传过来当作打卡人,否则随便换个 ID 就能给别人造假考勤。正确做法是从登录态里解析当前用户。SecurityUtils或类似的上下文工具负责在拦截器里把 JWT 载荷中保存的用户 ID 存起来,业务层只从工具类获取,不信任前端传参。

4.2 Service 层业务判断

考勤打卡的业务核心是判断当前时间是上班卡还是下班卡,并识别迟到状态。判断逻辑要放在 Service 层。下面给出简化示例:

@Service public class AttendanceServiceImpl implements AttendanceService { @Resource private AttendanceMapper attendanceMapper; @Resource private DeptService deptService; @Override public Attendance clockIn(Long userId, ClockInDTO dto) { LocalDate today = LocalDate.now(); LocalTime now = LocalTime.now(); Attendance record = attendanceMapper.selectByUserAndDate(userId, today); if (record == null) { record = new Attendance(); record.setUserId(userId); record.setAttDate(today); record.setClockInTime(LocalDateTime.now()); // 对比部门规定的上班时间,判断是否迟到 boolean late = now.isAfter(deptService.getWorkStartTime(userId)); record.setStatus(late ? AttendanceStatus.LATE : AttendanceStatus.NORMAL); attendanceMapper.insert(record); } else if (record.getClockOutTime() == null) { // 下班打卡,只更新字段,不新增记录 record.setClockOutTime(LocalDateTime.now()); attendanceMapper.updateById(record); } else { // 已经完成上下班打卡 throw new BusinessException("今日考勤已全部完成"); } return record; } }

这个逻辑对比很多直接把打卡时间存成列表的实现要严谨得多。它的核心思路是:一天只维护一条考勤记录,第一次打卡写上班时间,第二次打卡写下班时间,后续打卡次数直接拒绝。通过unique key兜底,数据库层面不会出现同一天多条主记录。

4.3 JWT 登录鉴权配置

在前后端分离项目中,SpringBoot 后端不再使用 Session,而是签发 Token。常用方案是 Spring Security + JWT,或者直接用拦截器实现。下面给出一个轻量的过滤链配置思路,合适做毕设或小型项目:

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource private JwtAuthenticationFilter jwtAuthenticationFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/captcha").permitAll() .antMatchers("/api/attendance/export").hasRole("ADMIN") .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }

需要注意一个版本差异:如果当前 SpringBoot 版本较高,通常对应 Spring Security 5.7 之后的写法,WebSecurityConfigurerAdapter已经废弃,你需要改用SecurityFilterChain的 Bean 方式:

@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests(auth -> auth .requestMatchers("/api/auth/login", "/api/auth/captcha").permitAll() .requestMatchers("/api/attendance/export").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }

如果你从网上下载的旧代码一直报WebSecurityConfigurerAdapter找不到,多半就是 SpringBoot 版本太高导致的。两个解决办法:降级 SpringBoot 到 2.7.x,或把安全配置改成新写法。建议优先改成新写法,毕竟要顺着新版本走。

4.4 MyBatis-Plus 操作与分页查询

MyBatis-Plus 能极大减少手写 SQL 的工作量。选员工列表、分页查询考勤记录等操作都是常规写法:

@Override public PageResult<AttendanceVO> pageRecord(AttendanceQueryDTO dto) { Page<Attendance> page = new Page<>(dto.getPageNum(), dto.getPageSize()); LambdaQueryWrapper<Attendance> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(dto.getUserId() != null, Attendance::getUserId, dto.getUserId()) .ge(dto.getStartDate() != null, Attendance::getAttDate, dto.getStartDate()) .le(dto.getEndDate() != null, Attendance::getAttDate, dto.getEndDate()) .orderByDesc(Attendance::getAttDate); Page<Attendance> result = attendanceMapper.selectPage(page, wrapper); // entity -> VO 转换由 convert 方法完成 return new PageResult<>(result.getTotal(), convert(result.getRecords())); }

如果涉及多表关联或复杂报表,例如“查员工姓名 + 部门名称 + 考勤状态”,再用自定义 SQL 会更舒服。MyBatis-Plus 的LambdaQueryWrapper更适合单表简单条件,别把所有查询都硬套在 Wrapper 上。

5. Vue3 前端项目实现要点

前端部分建议在 Vite 创建的项目基础上开发,先把依赖和目录结构梳理清楚。Vue3 项目里最核心的技术点不是页面样式,而是 Axios 请求封装、动态路由、Pinia 状态管理、权限按钮控制。

5.1 项目初始化与目录结构

npm create vite@latest attendance-web -- --template vue cd attendance-web npm install npm install element-plus axios pinia vue-router

推荐目录结构如下:

src ├── api // 接口请求函数,按模块拆分 ├── assets ├── components ├── layout // 后台布局框架,侧边栏、顶栏 ├── router // 路由配置与动态路由 ├── store // Pinia 状态管理 ├── utils // request 封装、工具函数 ├── views │ ├── login │ ├── dashboard │ ├── attendance │ ├── employee │ └── leave ├── App.vue └── main.js

5.2 Axios 请求封装

前后端分离项目里,Axios 封装是必须做的。统一处理 baseURL、Token 注入、错误提示和 401 跳转,否则每个页面都会出现一堆重复的异常处理代码。

// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import { useUserStore } from '@/store/user' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 15000 }) request.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '系统错误') return Promise.reject(new Error(res.msg || '系统错误')) } return res }, error => { if (error.response?.status === 401) { const userStore = useUserStore() userStore.logout() router.push('/login') ElMessage.error('登录状态已过期,请重新登录') } else { ElMessage.error(error.message || '网络异常') } return Promise.reject(error) } ) export default request

实际调用时再按模块封装一层,例如考勤模块的 API:

// src/api/attendance.js import request from '@/utils/request' export function clockIn(data) { return request.post('/attendance/clockIn', data) } export function getMyAttendance(params) { return request.get('/attendance/myRecords', { params }) } export function exportAttendance(params) { return request.post('/attendance/export', params, { responseType: 'blob' }) }

这种封装方式在双端联调时非常省心。后端改了接口地址,只需要在api文件夹调整路径;后端返回 401,所有页面都会自动跳到登录页,不用每个页面单独写。

5.3 Vue3 页面开发示例

登录页的核心工作不是处理 UI,而是把登录 Token 保存到 Pinia 和 localStorage,然后跳转到首页。下面是简化的登录逻辑:

<script setup> import { ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { login } from '@/api/auth' import { useUserStore } from '@/store/user' const router = useRouter() const userStore = useUserStore() const loginForm = ref({ username: 'admin', password: 'admin123' }) const handleLogin = async () => { if (!loginForm.value.username || !loginForm.value.password) { ElMessage.warning('请输入用户名和密码') return } const res = await login(loginForm.value) userStore.setToken(res.data.token) userStore.setRoles(res.data.roles) router.push('/dashboard') } </script>

考勤打卡页面的按钮状态管理是一个很实用的 Vue3 场景。可以用一个布尔值记录“今天是否已经打完卡”,按钮文案在“上班打卡”“下班打卡”“今日考勤已完成”之间切换。用计算属性computed派生按钮状态更合适,不要把 UI 状态散到多个ref里:

<script setup> import { computed, ref, onMounted } from 'vue' import { clockIn, getTodayAttendance } from '@/api/attendance' const attendanceInfo = ref(null) const clockLoading = ref(false) const todayStatusText = computed(() => { if (!attendanceInfo.value) return '今日尚未打卡' if (attendanceInfo.value.clockInTime && !attendanceInfo.value.clockOutTime) { return '已打上班卡,待下班打卡' } if (attendanceInfo.value.clockInTime && attendanceInfo.value.clockOutTime) { return '今日考勤已完成' } return '今日尚未打卡' }) const handleClockIn = async () => { clockLoading.value = true try { await clockIn() await loadToday() } finally { clockLoading.value = false } } const loadToday = async () => { const res = await getTodayAttendance() attendanceInfo.value = res.data } onMounted(loadToday) </script>

页面加载时先调用getTodayAttendance获取今天的考勤状态,再根据状态决定按钮是否可点。后端同时要做一个防御性判断:重复打卡直接返回业务异常,前端再拦截提示。

5.4 动态菜单与路由守卫

考勤管理系统的角色通常分管理员和普通员工。不同角色看到的菜单不同,管理员能看到员工管理、部门管理、考勤报表,普通员工只能看到个人信息和自己当月的考勤记录。菜单和路由建议由前端根据登录后返回的角色权限动态生成,或者通过路由守卫直接控制访问权限。

简洁做法是:在全局路由守卫里判断用户是否已登录,再结合用户菜单权限判断是否有权访问当前路由:

router.beforeEach((to, from, next) => { const userStore = useUserStore() if (!userStore.token) { if (to.path === '/login') return next() return next('/login') } if (to.path === '/login') return next('/dashboard') next() })

后台管理系统里,按下 F5 刷新后动态路由丢失也是一个经典坑。解决思路是在路由守卫中检查 Pinia 中是否已存放动态路由,若没有且用户已登录,则调用后端获取菜单,用router.addRoute动态注册,再重新进入目标页面。

5.5 Vue2 与 Vue3 数据请求差异

网上很多旧考勤项目还是 Vue2 + axios 的老写法,与 Vue3 对比后主要有几个差异:

对比点Vue2Vue3
创建项目vue create / webpacknpm create vite
数据响应式options API with data/computed/methodscomposition API with ref/reactive/computed
API 调用在 methods 中调用在 setup 或 script setup 中调用
组件通信$emit+$on或 EventBusemit+ props,推荐 Pinia
生命周期created/mounted/destroyedonMounted/onBeforeUnmount

如果想把网上找的 Vue2 老代码改成 Vue3 项目,最大的适配成本在响应式声明和路由/状态管理部分,原样复制到 Vue3 里大概率会报错。建议后端沿用 SpringBoot,前端直接重写 Vue3 页面,不要试图在 Vue3 项目里兼容 Vue2 的 options 写法。

6. 前后端联调与部署实践

前后端分离项目开发阶段有一个非常影响体验的配置:前端代理。本地开发时前端跑在 5173 端口,后端跑在 8080 端口,直接跨域。在 Vite 项目根目录的vite.config.js里配置代理是推荐方案:

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { host: '0.0.0.0', port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

配置完以后,前端请求/api/attendance/myRecords时会自动把请求代理到http://localhost:8080/api/attendance/myRecords。开发阶段不需要处理跨域,生产环境直接把前端打包后的dist目录交给 Nginx 托管,也是通过 Nginx 反向代理转发/api到 Java 服务,后端甚至可以完全不开启 CORS 配置。

后端打包命令:

mvn clean package -DskipTests

打包后的 jar 文件在target目录下,启动命令:

java -jar attendance-server.jar --server.port=8080

前端打包命令:

npm run build

打包产物在dist目录,把它放到 Nginx 的 html 目录或自己指定的 root 下即可。生产环境里注意修改后端application.yml的数据库地址和密码,不要把本地数据库连接信息直接打成正式包。典型的 Nginx 配置可以这样写:

server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

try_files $uri $uri/ /index.html;这一行很重要。Vue Router 默认使用 history 模式,直接刷新/attendance页面时,如果 Nginx 没有这行配置,就会返回 404。加上它以后,所有前端路由都回退到 index.html,再由前端路由接管。

7. 常见问题与排查方法

考勤系统在开发、部署和运行过程中会遇到不少固定问题。下面这几类出现频率最高,我按“现象 -> 原因 -> 处理”的方式整理成表。

问题现象可能原因排查方式解决方案
前端请求接口报跨域后端未配置跨域或代理未生效检查 Network 请求是否走了 /api优先用 Vite proxy / Nginx 代理,后端开启 CORS 只适合临时调试
登录成功后刷新又跳到登录页Vuex/Pinia 中状态没有持久化刷新页面查看浏览器 Application 中是否有 token登录后将 token 写入 localStorage,页面加载时先从 localStorage 恢复
按 F5 刷新子页面显示 404前端 history 路由没有配置 try_files看 Nginx 日志或直接访问页面路由Nginx 添加try_files $uri $uri/ /index.html;
使用 WebSecurityConfigurerAdapter 报错SpringBoot 版本太高,Security 写法过时查看 Maven 依赖版本改写成 SecurityFilterChain Bean,或降低 SpringBoot 版本
MyBatis-Plus 自动填充不生效未配置 MetaObjectHandler插入数据后查看 create_time 是否为空配置 MyBatis-Plus 自动填充处理器
打卡按钮一直提示别频率太快表里加了唯一索引,插入时报重复异常看后端日志是否有 DuplicateKeyException捕获异常后查询已有记录,更新上下班时间,不覆盖旧记录
考勤导出 Excel 乱码未设置 Content-Disposition 编码打开文件检查字符集设置响应头attachment; filename*=UTF-8''
中文乱码数据库连接 URL 未加 useUnicode看数据库字符集与表字符集JDBC URL 增加characterEncoding=utf8,数据表统一 utf8mb4
npm install 后运行报 Node 版本不支持Vite 要求 Node 18+执行 node -v升级 Node 版本或使用 nvm 切换

对于考勤系统的导出功能,建议前端使用responseType: 'blob'接收文件流,后端用 Easy Excel 或 POI 生成 Excel。如果导出中文文件名总是乱码,重点检查响应头里的Content-Disposition,标准的写法是attachment; filename*=UTF-8''%E8%80%83%E5%8B%A4.xlsx,不要只写filename=xxx.xlsx

8. 考勤系统的最佳实践与工程建议

最后补几条方向性的建议。这些不是代码层面的硬规范,而是把这个项目做得更完整、更值得放进简历或毕设提报里的思路。

第一,把考勤规则配置化,不要写死在代码里。在每个 Controller 里判断上下班时间是不合理的。更好做法是在sys_dept或专门参数表中存上班时间、下班时间、迟到宽容分钟数,管理员可以在系统设置页面维护。这样答辩时直接介绍“考勤规则支持部门差异化配置”,业务深度会明显提升。

第二,写清楚数据闭环。员工请假通过后,att_attendance表中对应日期应更新为请假状态,或者每日定时任务会自动为请假期间的员工生成请假考勤记录。月底统计时,出勤天数 = 正常 + 迟到 + 早退,请假天数指审批通过并完成销假的记录。如果每个模块的数据都能串成一条线,系统的完成度会非常高。

第三,补一个定时任务。SpringBoot 里用@Scheduled写一个每天凌晨 3 点的任务,扫描昨天没有打卡记录的员工,自动补齐缺卡状态。这个功能真实项目很需要,也方便在答辩中讲解“为什么需要定时任务”。

@Component public class AttendanceScheduledTask { @Resource private AttendanceService attendanceService; @Scheduled(cron = "0 0 3 * * ?") public void autoMarkMissing() { attendanceService.markMissingForYesterday(); } }

启动类记得打开定时任务开关:

@SpringBootApplication @EnableScheduling public class AttendanceApplication { public static void main(String[] args) { SpringApplication.run(AttendanceApplication.class, args); } }

第四,批量导出和导出任务分离。当考勤数据量变大以后,导出的执行时间会明显增加,点击导出按钮如果一直转圈,体验很差。稳妥的设计是点击导出后生成后台任务,任务完成后再下载,或者直接限制导出时间范围的方式,比如后端限制单次导出跨度不能超过一个月。如果只是毕设或 Demo 项目,直接在导出接口里限制时间范围就能解决大部分问题。

第五,权限设计只要把“管理员”和“员工”两种角色做好就足够。很多考勤系统做角色管理时贪多,做出五六张表、十几种角色,最后反而不好演示。从实际角度出发,把员工和管理员两条权限链路彻底跑通,已经保证这个项目的完整度。

第六,真机部署注意安全。后端数据库不要用 root 账号,单独建一个业务账号,只授予该系统数据库的增删改查权限。生产环境开启 HTTPS,Nginx 层限制请求体大小,避免上传异常文件。接口返回不要暴露数据库表的原始字段,尤其密码字段必须做加密且不能返回前端。

这套 SpringBoot + Vue3 的员工考勤管理系统,适合作为第一个完整全栈项目来练手,也适合快速改造成毕设或课程设计。日常可以先从登录模块开始跑链路,再逐个补员工模块、考勤打卡、请假审批和报表统计,每个模块之间注意保持数据联动。在部署时优先把 Vite 代理和 Nginx 反向代理跑通,遇到 404 或跨域问题基本都能从“后端端口、代理配置、静态资源路径”三个方向定位。如果看完这篇想自己动手搭,建议第一件事不是写代码,而是把数据库表和角色权限模型先画出来,后面所有功能都会围绕这张模型展开。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!