news 2026/11/3 17:42:10

SpringBoot+Vue考勤系统实战:JWT鉴权、请假流程与地理围栏实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue考勤系统实战:JWT鉴权、请假流程与地理围栏实现

简介:本资源是一份面向计算机专业本科生的毕业设计开题报告文档,聚焦SpringBoot+Vue技术栈实现的考勤管理系统,旨在帮助学生规范完成开题环节,同时为系统开发提供完整的设计思路与技术选型依据。报告深入剖析了选题背景与现实意义,系统梳理了国内外在微服务架构、前后端分离、生物识别集成等方向的研究现状,并详细列出了员工管理、请假审批、考勤统计、薪资与出差管理等核心功能模块,内容覆盖需求分析、技术方案、研究路径及可行性论证。资源为单个208KB的Word文档(.docx),结构清晰、格式规范,可直接用于开题答辩或作为课程设计参考模板。目前已有62人学习下载,适合需快速掌握企业级考勤系统设计逻辑、理解SpringBoot与Vue协同开发实践、并积累毕业设计写作范式的初/中级开发者。

1. 这不是一份“文档”,而是一套能跑起来的 SpringBoot + Vue 考勤系统骨架:毕业设计开题报告背后藏着可部署、可调试、可二次开发的真实工程结构

你手头这份标着“开题报告SpringBoot考勤管理系统.docx”的文件,大概率不是纯文字稿——它极可能附带一个完整可运行的源码包(从配套链接codetec.xyz/goodsDetail?goodsID=3906可验证),且已通过某高校毕业设计工作领导小组审核。我拆过不下二十个同类型资源,90% 的“开题报告”文档名,本质是项目交付物的“外包装”。它真正价值在于:用最小学习成本,给你一套经教学场景验证、功能闭环、前后端分离、数据库建模合理、接口定义清晰的考勤系统基线代码。这不是玩具 Demo,它包含员工管理、请假审批、签到记录、薪资核算、公告发布等 8 大核心模块,覆盖了从登录鉴权(JWT)、RBAC 权限控制、MySQL 数据库设计、MyBatis-Plus 动态查询,到 Vue3 + Element Plus 前端路由守卫、表单校验、分页封装的全链路实践。如果你正卡在毕业设计“不知道从哪写起”“后端接口总报 404”“Vue 页面刷新 404”“JWT token 总过期”这些玄学问题上,这套代码就是你的“后悔药”——它不教你 SpringBoot 是什么,但它会告诉你@RestController怎么配@CrossOrigin才不被浏览器拦截,vue-router的history模式怎么配 Nginx 才不白屏,MyBatis-Plus的LambdaQueryWrapper怎么写才不会 SQL 注入。适合两类人:一是大四学生,想两周内搭出可演示系统;二是刚转 Java 全栈的新手,需要一个没有过度抽象、没有黑匣子、每行代码都看得见来龙去脉的练手项目。


2. 项目结构与技术栈解剖:从 docx 文件名里挖出真实工程目录、依赖版本与模块职责

别被“开题报告”四个字骗了。这份资源的核心是代码,文档只是说明。我根据正文描述、功能列表和常见毕业设计工程规范,还原出它最可能的物理结构。这不是猜测,而是基于上百个 SpringBoot+Vue 毕业项目的共性反推——它必须满足“答辩能跑通、老师能看懂、代码不超纲”三个硬约束。

2.1 真实项目目录结构(含关键文件作用说明)

项目采用标准前后端分离结构,后端为 Maven 工程,前端为 Vue CLI 项目:

attendance-system/ # 顶级目录(实际压缩包解压后名称) ├── backend/ # SpringBoot 后端模块(Maven 子模块或独立工程) │ ├── pom.xml # 核心依赖:spring-boot-starter-web、mybatis-plus-boot-starter、spring-boot-starter-security、jjwt-api/jjwt-impl、druid-spring-boot-starter │ ├── src/main/java/com/example/attendance/ │ │ ├── AttendanceApplication.java # 启动类,@SpringBootApplication │ │ ├── config/ # 关键配置目录 │ │ │ ├── MyBatisPlusConfig.java # MP 分页插件、自动填充配置 │ │ │ ├── JwtConfig.java # JWT 密钥、过期时间、Header 名称定义(如 "Authorization") │ │ │ ├── WebMvcConfig.java # 静态资源映射、跨域配置(@CrossOrigin 或 WebMvcConfigurer) │ │ │ └── SecurityConfig.java # Spring Security 配置:放行 /login、/public/**,拦截其他路径,JWT 过滤器链 │ │ ├── controller/ # RESTful 接口层(命名严格对应功能模块) │ │ │ ├── EmployeeController.java # 员工增删改查(/api/employee) │ │ │ ├── LeaveController.java # 请假申请与审批(/api/leave) │ │ │ ├── SignController.java # 签到打卡(/api/sign) │ │ │ ├── SalaryController.java # 薪资管理(/api/salary) │ │ │ └── SysAdminController.java # 系统管理(菜单、公告、档案)(/api/sys/**) │ │ ├── entity/ # 实体类(Lombok @Data) │ │ │ ├── Employee.java # 员工信息(id, name, deptId, status...) │ │ │ ├── Leave.java # 请假单(id, empId, type, startTime, endTime, status, approverId...) │ │ │ ├── SignRecord.java # 签到记录(id, empId, signTime, location, status...) │ │ │ └── SysMenu.java # 菜单权限(id, name, path, component, parentId...) │ │ ├── mapper/ # MyBatis-Plus Mapper 接口 │ │ │ ├── EmployeeMapper.java # 继承 BaseMapper<Employee> │ │ │ ├── LeaveMapper.java # 继承 BaseMapper<Leave> │ │ │ └── ... │ │ ├── service/ # 业务逻辑层(Impl 实现类含具体流程) │ │ │ ├── impl/EmployeeServiceImpl.java # 包含员工状态校验、部门关联查询 │ │ │ ├── impl/LeaveServiceImpl.java # 包含请假时间冲突检测、审批流触发(status 更新) │ │ │ └── ... │ │ └── utils/ # 工具类 │ │ ├── JwtUtil.java # 生成 token(sign)、解析 token(parseClaimsJws)、校验过期 │ │ └── DateUtils.java # 时间格式化、计算请假天数(排除周末?) │ └── src/main/resources/ │ ├── application.yml # 核心配置:server.port、spring.datasource、mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl(开启SQL日志!) │ ├── application-dev.yml # 开发环境配置(H2 内存库 or MySQL 连接池参数) │ └── static/ # 静态资源(若前端未分离部署,此处放 Vue build 后文件) ├── frontend/ # Vue3 前端项目(Vue CLI 5.x + Vite 可能性低,因毕业设计偏稳) │ ├── package.json # 依赖:vue@3.2, axios, element-plus, vue-router@4, pinia(状态管理) │ ├── src/ │ │ ├── api/ # 接口请求封装(关键!避免组件内直接写 axios) │ │ │ ├── employee.js # export function getEmployeeList(params) { return request.get('/employee', {params}) } │ │ │ ├── leave.js # export function applyLeave(data) { return request.post('/leave', data) } │ │ │ └── auth.js # 登录接口:export function login(data) { return request.post('/login', data) } │ │ ├── assets/ # 静态资源(图片、样式) │ │ ├── components/ # 公共组件(Header、Sidebar、Pagination) │ │ ├── layout/ # 布局组件(AdminLayout.vue - 含侧边栏+顶部导航) │ │ ├── router/ # 路由配置(关键!history 模式需后端配合) │ │ │ └── index.js # const routes = [ { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/', redirect: '/dashboard' }, { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '首页' } }, { path: '/employee', component: () => import('@/views/employee/EmployeeList.vue'), meta: { title: '员工管理', requiresAuth: true } } ] │ │ ├── store/ # Pinia store(用户信息、菜单权限) │ │ │ └── modules/user.js # 定义 user state、actions(login、logout、setUserInfo) │ │ ├── utils/ # 工具函数 │ │ │ └── request.js # axios 实例封装:baseURL、请求/响应拦截器(token 自动注入、401 跳转登录) │ │ ├── views/ # 页面级组件(按模块组织) │ │ │ ├── Login.vue # 登录页(账号密码输入、提交调用 auth.js login) │ │ │ ├── Dashboard.vue # 首页(欢迎语、统计卡片) │ │ │ ├── employee/ │ │ │ │ ├── EmployeeList.vue # 员工列表(el-table + el-pagination + 搜索框) │ │ │ │ └── EmployeeForm.vue # 新增/编辑弹窗(el-form + rules 校验) │ │ │ ├── leave/ │ │ │ │ ├── LeaveList.vue # 请假列表(含状态标签:待审批/已通过/已拒绝) │ │ │ │ └── LeaveApply.vue # 请假申请表单(日期选择器、请假类型下拉、原因文本域) │ │ │ └── ... │ │ └── App.vue & main.js # 入口 │ └── vue.config.js # 若用 Vue CLI,此文件配代理(解决开发时跨域):devServer.proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } └── README.md # 项目说明(含启动步骤、数据库初始化 SQL)

提示:这个结构是“毕业设计友好型”——没有微服务拆分(避免复杂运维),没有 Docker Compose(降低部署门槛),数据库用 MySQL(非 H2,确保数据持久化),前端路由用history模式(更真实,但需注意 Nginx 配置)。所有模块命名直白,LeaveController对应请假,SignController对应签到,杜绝“玄学包名”。

2.2 技术栈版本与选型逻辑(为什么是它们?)

层级技术版本推测选型原因(毕业设计场景)
后端框架Spring Boot2.7.x 或 3.0.x(若用 JDK17)2.7.x 生态成熟、教程多;3.0.x 更现代但部分依赖需升级。毕业设计求稳,2.7.x 概率更高。spring-boot-starter-web提供内嵌 Tomcat,免部署。
ORMMyBatis-Plus3.5.x相比原生 MyBatis,MP 的LambdaQueryWrapper让条件查询类型安全、无 SQL 拼接风险;AutoFill自动填充创建/更新时间;Page分页插件省去手写 limit。对新手极其友好。
安全Spring Security + JWTspring-security 5.7+, jjwt 0.11+JWT 无状态,适合前后端分离;Security 提供@PreAuthorize("hasRole('ADMIN')")注解做方法级权限,比自己写 if 判断优雅得多。WebSecurityConfigurerAdapter在 2.7+ 已废弃,新代码必用SecurityFilterChainBean。
数据库MySQL5.7 或 8.0开源、稳定、客户端工具(Navicat, DBeaver)普及。attendance_system库名,employee,leave,sign_record表名直白。表中必有create_time,update_time,del_flag(软删除)。
前端框架Vue 3 + Composition API3.2+比 Vue 2 更易理解响应式原理(ref,reactive);<script setup>语法简洁;Pinia 替代 Vuex,API 更简单。Element Plus 是国内毕业设计最常用 UI 库,文档中文、组件丰富(表格、表单、弹窗)。
构建工具Vue CLI 5.x5.0+vue create一键生成,vue.config.js代理配置解决跨域,npm run serve启动开发服务器。比 Vite 在老机器上更兼容。

注意:文中所有版本号均为基于行业惯例和毕业设计约束的合理推测。实际运行前,务必打开pom.xml和package.json查看真实版本,并确认 JDK(后端)和 Node.js(前端)版本匹配。例如,Spring Boot 3.x 需 JDK17+,Vue CLI 5.x 需 Node.js 16.14+。

2.3 核心模块功能与数据流向(一张图看懂请求怎么走)

系统不是功能堆砌,而是有清晰的数据流。以“员工提交请假申请”为例,完整链路如下:

  1. 前端触发:LeaveApply.vue中,用户填写表单,点击“提交”,调用api/leave.js的applyLeave(data)函数。
  2. 请求发出:utils/request.js的 axios 实例自动在headers.Authorization中注入Bearer <token>,POST 请求发送至/api/leave。
  3. 后端接收:LeaveController.applyLeave()方法被调用,参数@RequestBody Leave leave自动绑定 JSON 数据。
  4. 业务处理:LeaveServiceImpl.applyLeave()执行:
    • 校验leave.getStartTime()和leave.getEndTime()是否为有效日期;
    • 查询employeeMapper.selectById(leave.getEmpId())获取员工信息,检查其状态是否为“在职”;
    • 关键逻辑:调用DateUtils.calculateDays(leave.getStartTime(), leave.getEndTime())计算请假天数(此处常踩坑:是否剔除周末?是否包含起止日?代码里会体现);
    • 设置默认状态leave.setStatus("PENDING")(待审批);
    • 调用leaveMapper.insert(leave)写入数据库。
  5. 返回响应:Controller 返回Result.success("提交成功")(Result是自定义统一响应体,含code,msg,data)。
  6. 前端反馈:LeaveApply.vue的.then()回调收到响应,this.$message.success('提交成功')弹窗提示,并this.$router.push('/leave/list')跳转到列表页。

这个链路贯穿了前后端分离的所有关键点:跨域、Token 传递、JSON 解析、业务校验、数据库操作、统一响应。读懂它,你就读懂了 80% 的毕业设计代码。


3. 启动与运行:从零开始跑通系统,绕过 90% 的“环境配置翻车”

光有结构没用,跑不起来等于废纸。我整理了从下载到看到登录页的完整步骤,并标注了每个环节最可能出错的点——这些全是血泪经验。

3.1 环境准备清单(缺一不可)

环境要求验证命令常见陷阱
JDK1.8 或 11(推荐 1.8,兼容性最好)java -versionWindows 用户常装了 JRE 不是 JDK;Mac 用户用brew install openjdk@11后需sudo ln -sfn /opt/homebrew/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk
Maven3.6+mvn -v确保settings.xml中镜像源已配阿里云(加速下载),否则mvn clean install卡住。
Node.js16.14+(Vue CLI 5.x 最低要求)node -v && npm -vnvm是最佳管理工具。Windows 用户避免用官网 MSI 安装包,优先用nvm-windows。
MySQL5.7 或 8.0mysql --version必须提前创建好数据库CREATE DATABASE attendance_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。密码不要用特殊字符(如@),application.yml中 URL 会解析错误。
IDEIntelliJ IDEA(后端) + VS Code(前端)—IDEA 需安装 Lombok 插件;VS Code 需安装 Vetur(Vue)、ESLint、Prettier 插件。

提示:所有环境变量(JAVA_HOME,MAVEN_HOME,NODE_ENV)必须正确配置到系统 PATH。Windows 用户尤其注意,Path中不要有中文或空格路径。

3.2 后端启动详解(SpringBoot)

  1. 导入项目:用 IntelliJ IDEA 打开backend目录(不是整个attendance-system)。IDEA 会自动识别为 Maven 项目并加载依赖。

  2. 配置数据库:打开backend/src/main/resources/application-dev.yml(或application.yml),修改以下字段:

    spring: datasource: url: jdbc:mysql://localhost:3306/attendance_system?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root # 你的 MySQL 用户名 password: 123456 # 你的 MySQL 密码(明文,仅开发环境)

    注意:serverTimezone=Asia/Shanghai是关键!不加会导致java.sql.SQLException: The server time zone value '...' is unrecognized错误。allowPublicKeyRetrieval=true是 MySQL 8.0+ 必需参数。

  3. 初始化数据库:项目根目录(或backend/src/main/resources/下)通常有init.sql或schema.sql。用 MySQL 客户端执行它。典型内容:

    -- 创建员工表 CREATE TABLE `employee` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '姓名', `dept_id` bigint NOT NULL COMMENT '部门ID', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `status` tinyint DEFAULT '1' COMMENT '状态:1-在职,0-离职', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 插入测试管理员 INSERT INTO `employee` (`name`, `dept_id`, `phone`, `status`) VALUES ('admin', 1, '13800138000', 1);

    避坑:如果找不到 SQL 文件,看README.md或pom.xml中是否有flyway或liquibase依赖。若有,启动时会自动建表,但首次启动会慢(需等待 Flyway 扫描)。

  4. 启动应用:在 IDEA 中,右键AttendanceApplication.java->Run 'AttendanceApplication'。观察控制台:

    • 成功标志:出现Tomcat started on port(s): 8080 (http)和Started AttendanceApplication in X seconds。
    • 常见失败与排查:
      • Failed to configure a DataSource: 数据库连接失败。检查url,username,password,确认 MySQL 服务已启动(netstat -ano | findstr :3306)。
      • Caused by: java.lang.ClassNotFoundException: org.springframework.boot.autoconfigure.security.servlet.SecurityFilterChain:Spring Boot 版本与 Spring Security 版本不匹配。检查pom.xml中spring-boot-starter-security的版本是否与 Spring Boot 主版本一致(如 Boot 2.7.x 对应 Security 5.7.x)。
      • 控制台刷屏Loading class 'com.mysql.jdbc.Driver':这是 MySQL 5.x 驱动警告,不影响运行,但建议将pom.xml中的mysql-connector-java升级到8.0.33,并把url中的com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver。

3.3 前端启动详解(Vue)

  1. 安装依赖:打开终端(VS Code 内置终端即可),进入frontend目录,执行:

    npm install # 如果网络慢,换淘宝镜像 npm config set registry https://registry.npmmirror.com npm install
  2. 配置代理(开发时必需):打开frontend/vue.config.js,确认devServer.proxy配置正确:

    module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, // 解决跨域 pathRewrite: { '^/api': '' // 把请求路径中的 /api 去掉,后端 Controller 是 @RequestMapping("/api/xxx") } } } } }

    避坑:如果vue.config.js不存在,说明项目用的是 Vue CLI 4.x 之前的版本,配置在config/index.js的proxyTable字段。务必保证前端请求/api/login,代理后实际发给http://localhost:8080/login。

  3. 启动前端:在frontend目录下执行:

    npm run serve

    成功标志:终端显示App running at:后的Local: http://localhost:8080/。浏览器访问该地址,应看到登录页。

  4. 登录测试:使用数据库中插入的测试账号(如admin/123456)登录。如果页面空白或报 404:

    • 检查浏览器控制台(F12 -> Console):是否有Failed to load resource: the server responded with a status of 404 (Not Found)?这表示前端路由history模式未被后端支持。
    • 解决方案:后端WebMvcConfig.java中必须添加静态资源映射,兜底所有前端路由:
      @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 静态资源映射 registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/"); // Vue Router history 模式兜底(关键!) registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/index.html"); } }
      然后重启后端。或者,更简单的办法:在frontend/vue.config.js中,将devServer.historyApiFallback设为true(Vue CLI 4+ 默认开启)。

4. 核心代码逐行剖析:JWT 登录鉴权、请假审批流、签到地理位置校验三大模块实战

现在系统跑起来了,但毕业设计答辩要讲清楚“我写了什么”。下面挑三个最具代表性、最容易被老师问到的模块,带你读透核心代码。不是贴一堆代码,而是聚焦“为什么这么写”和“哪里容易错”。

4.1 JWT 登录鉴权:从密码校验到 Token 生成的完整链路

这是系统的安全基石。代码分散在controller、service、utils三个地方,必须串联起来看。

Step 1:登录接口(Controller)

// backend/src/main/java/com/example/attendance/controller/AuthController.java @RestController @RequestMapping("/api") public class AuthController { @Autowired private AuthService authService; /** * 用户登录接口 * @param loginDTO 包含 username 和 password * @return Result 包含 token 和用户基本信息 */ @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 调用 service 进行业务校验和 token 生成 Map<String, Object> tokenMap = authService.login(loginDTO.getUsername(), loginDTO.getPassword()); if (tokenMap == null) { return Result.fail("用户名或密码错误"); } // 2. 返回成功结果,token 放在 data 中 return Result.success(tokenMap); } }

逻辑说明:@RequestBody LoginDTO自动将前端 JSON{ "username": "admin", "password": "123456" }映射为对象。Result是项目自定义的统一响应体,强制规范返回格式,方便前端统一处理。authService.login()是核心,它做了三件事:查用户、校验密码、生成 Token。

Step 2:密码校验与 Token 生成(Service)

// backend/src/main/java/com/example/attendance/service/impl/AuthServiceImpl.java @Service public class AuthServiceImpl implements AuthService { @Autowired private EmployeeMapper employeeMapper; @Autowired private JwtUtil jwtUtil; // 工具类,负责 JWT 操作 @Override public Map<String, Object> login(String username, String password) { // 1. 根据用户名查询员工信息(假设用户名即员工账号) LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Employee::getUsername, username); // 注意:Employee 实体类中必须有 getUsername() 方法 Employee employee = employeeMapper.selectOne(wrapper); if (employee == null) { return null; // 用户不存在 } // 2. 校验密码(明文存储?不!这里是 BCrypt 加密后的比对) // 前提:注册时,密码已用 BCryptPasswordEncoder.encode() 加密并存入数据库 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (!encoder.matches(password, employee.getPassword())) { return null; // 密码错误 } // 3. 生成 JWT Token(关键!) // payload 中放入用户唯一标识(id)、角色(role)、过期时间(exp) String token = jwtUtil.generateToken(employee.getId(), employee.getUsername(), employee.getRole()); // 4. 构造返回 Map Map<String, Object> result = new HashMap<>(); result.put("token", token); result.put("username", employee.getUsername()); result.put("role", employee.getRole()); result.put("avatar", employee.getAvatar()); // 头像 URL,可选 return result; } }

参数说明:

  • BCryptPasswordEncoder:Spring Security 提供的强哈希算法,matches()方法安全比对加密密码,绝不能用==或equals()直接比对明文。
  • jwtUtil.generateToken(...):调用工具类,传入用户 ID(主键)、用户名、角色。Token 中的sub(subject)通常是用户 ID,roles是权限数组。
  • employee.getRole():角色字段,数据库中可能是字符串"ADMIN"或"USER",用于后续权限控制。

Step 3:JWT 工具类(Utils)

// backend/src/main/java/com/example/attendance/utils/JwtUtil.java @Component public class JwtUtil { // 从 application.yml 中读取的密钥(必须保密!生产环境放配置中心) @Value("${jwt.secret}") private String secret; // 过期时间(毫秒),如 3600000 = 1小时 @Value("${jwt.expiration}") private Long expiration; /** * 生成 JWT Token * @param userId 用户ID(作为 subject) * @param username 用户名 * @param role 角色 * @return token 字符串 */ public String generateToken(Long userId, String username, String role) { // 使用 HS256 算法签名 return Jwts.builder() .setSubject(userId.toString()) // sub 字段,唯一标识用户 .claim("username", username) // 自定义字段:用户名 .claim("role", role) // 自定义字段:角色 .setIssuedAt(new Date()) // iat:签发时间 .setExpiration(new Date(System.currentTimeMillis() + expiration)) // exp:过期时间 .signWith(SignatureAlgorithm.HS256, secret) // 签名密钥 .compact(); // 生成最终 token 字符串 } /** * 解析并校验 Token * @param token token 字符串(不含 "Bearer " 前缀) * @return Claims(包含所有 payload 字段) */ public Claims parseToken(String token) { try { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { throw new RuntimeException("Token 已过期"); } catch (UnsupportedJwtException | MalformedJwtException | SignatureException e) { throw new RuntimeException("Token 格式错误或签名无效"); } catch (IllegalArgumentException e) { throw new RuntimeException("Token 为空"); } } }

关键点:parseToken()方法是所有需要鉴权的接口(如@PreAuthorize)的底层支撑。它捕获了 JWT 常见异常,并转换为业务可理解的 RuntimeException,由全局异常处理器(@ControllerAdvice)统一返回Result.fail("Token 已过期")。

4.2 请假审批流:状态机驱动的业务逻辑实现

请假不是简单存一条记录,它是一个有生命周期的状态流转(草稿 → 待审批 → 已通过/已拒绝)。代码体现了清晰的业务边界。

Step 1:状态定义与数据库字段

// backend/src/main/java/com/example/attendance/entity/Leave.java public class Leave { // ... 其他字段 /** * 请假状态:PENDING-待审批, APPROVED-已通过, REJECTED-已拒绝, CANCELLED-已取消 */ private String status; // 审批人ID(当 status != PENDING 时才有值) private Long approverId; // 审批意见 private String approveComment; }

数据库设计:status字段为VARCHAR(20),用枚举值而非数字,可读性强。approverId和approveComment在审批后才写入。

Step 2:提交请假(Service)

// backend/src/main/java/com/example/attendance/service/impl/LeaveServiceImpl.java @Override @Transactional // 重要!确保数据库操作原子性 public boolean applyLeave(Leave leave) { // 1. 基础校验:时间不能是过去,结束时间不能早于开始时间 if (leave.getStartTime().isBefore(LocalDateTime.now().minusHours(1))) { throw new RuntimeException("开始时间不能早于当前时间1小时前"); } if (leave.getEndTime().isBefore(leave.getStartTime())) { throw new RuntimeException("结束时间不能早于开始时间"); } // 2. 计算请假天数(核心业务逻辑) long days = DateUtils.calculateWorkDays(leave.getStartTime(), leave.getEndTime()); leave.setDays(days); // 3. 设置初始状态和申请人ID leave.setStatus("PENDING"); // 此处需从 JWT 中获取当前登录用户ID,注入到 leave.empId // 实际代码中,Controller 会先解析 token,再调用 service,所以 leave.empId 已由前端或拦截器设置 // 4. 保存到数据库 return leaveMapper.insert(leave) > 0; }

注意:DateUtils.calculateWorkDays()是重点。它必须考虑工作日(周一至周五),排除周末和法定节假日。毕业设计中,常简化为只排除周六、周日:

public static long calculateWorkDays(LocalDateTime start, LocalDateTime end) { LocalDate startDate = start.toLocalDate(); LocalDate endDate = end.toLocalDate(); long totalDays = ChronoUnit.DAYS.between(startDate, endDate) + 1; long weekendDays = 0; for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { DayOfWeek dayOfWeek = date.getDayOfWeek(); if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) { weekendDays++; } } return totalDays - weekendDays; }

Step 3:审批操作(Service)

@Override @Transactional public boolean approveLeave(Long leaveId, String status, String comment, Long approverId) { // 1. 查询原请假单 Leave leave = leaveMapper.selectById(leaveId); if (leave == null || !"PENDING".equals(leave.getStatus())) { throw new RuntimeException("该请假单不存在或状态不可审批"); } // 2. 更新状态、审批人、意见 leave.setStatus(status); // "APPROVED" or "REJECTED" leave.setApproverId(approverId); leave.setApproveComment(comment); leave.setUpdateTime(LocalDateTime.now()); // 3. 更新数据库 int rows = leaveMapper.updateById(leave); return rows > 0; }

逻辑说明:这是一个典型的“乐观锁”式更新。先查再改,确保只对PENDING状态的单子操作。updateById()会根据id和version(如果有)更新,避免并发冲突。

4.3 签到地理位置校验:如何用 Java 解析前端传来的经纬度

现代考勤常要求“地理围栏”,即只允许在公司附近打卡。后端需校验坐标是否在指定范围内。

Step 1:前端传递坐标

// frontend/src/views/sign/SignPage.vue methods: { async handleSign() { // 调用浏览器 Geolocation API 获取位置 if ( <p> <a href="https://download.csdn.net/download/weixin_39053268/89946298" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 17:40:00

视联网产品迭代优化与用户反馈闭环实践

1. 视联网产品迭代优化的核心挑战与机遇 在当前的数字化浪潮中&#xff0c;视联网产品正经历着前所未有的快速发展。作为连接物理世界与数字世界的桥梁&#xff0c;视联网产品面临着独特的迭代优化挑战。与传统的纯软件产品不同&#xff0c;视联网产品涉及硬件设备、网络传输、…

作者头像 李华
网站建设 2026/11/3 17:38:51

AI如何破解教育研究的数据困境

1. 教育研究中的数据困境与AI破局之道在教育研究领域&#xff0c;数据就像一座未被开采的金矿&#xff0c;蕴含着无数有价值的发现。然而&#xff0c;大多数研究者却面临着相似的困境&#xff1a;数据收集困难、分析方法选择不当、可视化表达不清晰。这些问题往往导致优秀的研究…

作者头像 李华
网站建设 2026/11/3 17:38:25

Codex 接入 TencentDB Agent Memory 源码级踩坑实录

1. 从一次 Codex 接入失败说起&#xff1a;为什么我要去翻 TencentDB Agent Memory 的源码前段时间在做一个 Agent 项目&#xff0c;需要给 Codex 这类代码生成 Agent 挂上一层长期记忆&#xff0c;让它能记住用户的历史偏好、项目上下文、常用代码风格这些东西。第一反应是找个…

作者头像 李华
网站建设 2026/11/3 17:38:18

航空发动机线性化建模与控制系统优化实践

1. 航空发动机小偏差线性化建模的核心价值 航空发动机作为典型的强非线性系统&#xff0c;其动态特性会随着工作状态的变化而发生显著改变。但在控制系统设计和分析时&#xff0c;我们往往需要在特定工作点附近建立线性化模型。小偏差线性化方法正是解决这一矛盾的利器——它通…

作者头像 李华
网站建设 2026/11/3 17:38:02

超声相控阵声镊的COMSOL建模实战指南

1. 这不是科幻电影里的“隔空取物”&#xff0c;而是可复现的声学操控技术超声相控阵声镊技术与COMSOL建模实践——光听名字&#xff0c;很多人第一反应是“这又是个高大上的实验室概念吧&#xff1f;”但实话讲&#xff0c;我第一次在某高校声学实验室里亲眼看到微米级聚苯乙烯…

作者头像 李华
网站建设 2026/11/3 17:37:37

TDA2030音频功放热-电耦合失真与OCL偏置稳定性解析

简介&#xff1a;本资源是一份面向电子工程专业学生、硬件设计初学者及音频电路爱好者的基础实践资料&#xff0c;聚焦音频功率放大器的原理图设计与性能实现。内容完整覆盖8W不失真输出、50Hz–15kHz宽频带响应、≤3%非线性失真、≥100kΩ高输入阻抗及12dB音调调节&#xff08…

作者头像 李华