尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SpringBoot+Vue企业人事管理系统实战:从零搭建前后端分离HRM

SpringBoot+Vue企业人事管理系统实战:从零搭建前后端分离HRM 最近在帮朋友公司做内部管理系统升级发现很多中小企业在人事管理上还在用Excel表格来回传文件员工信息更新不及时、考勤统计效率低、薪资计算容易出错。正好借着这个机会我设计并实现了一套基于SpringBoot和Vue的前后端分离企业人事管理系统。这套系统不仅功能完整代码结构清晰而且非常适合作为毕业设计或企业内部二次开发的参考项目。本文将手把手带你从零开始完成整个系统的搭建并附上完整的源码和核心设计思路确保你能看懂、能运行、能扩展。1. 系统核心概念与设计目标在开始敲代码之前我们首先要明确这个系统要解决什么问题以及它的技术边界在哪里。一个企业人事管理系统HRM的核心是围绕“人”和“事”进行数字化管理旨在提升人力资源部门的工作效率规范管理流程。1.1 系统要解决的核心痛点信息孤岛员工基本信息、合同、考勤、薪资数据分散难以统一查询和分析。流程繁琐入职、转正、离职、请假、报销等流程依赖纸质或线下沟通效率低下且不易追溯。统计困难月度考勤汇总、薪资计算依赖人工工作量大且易出错。权限混乱不同角色如普通员工、部门经理、HR、管理员能看到和操作的数据范围应有严格区分。1.2 本系统设计目标基于以上痛点我们设计的系统需要达成以下目标功能模块化将系统清晰划分为组织架构、员工信息、考勤、薪资、审批等独立模块便于理解和开发。前后端分离前端使用Vue构建用户界面后端使用SpringBoot提供RESTful API两者通过JSON交互降低耦合度便于独立开发和部署。权限控制精细化基于角色RBAC进行访问控制确保数据安全。操作简便前端界面力求简洁直观减少用户学习成本。代码可维护性高采用分层架构规范命名添加必要注释方便后续迭代。1.3 技术栈选型说明后端SpringBoot简化Spring应用的初始搭建和开发过程内嵌Tomcat无需打包成WAR部署。配合MyBatis-Plus进行数据层操作极大减少SQL编写工作。前端Vue 2.x Element UIVue框架易于上手数据驱动视图的理念非常适合管理后台类应用。Element UI提供了丰富且美观的桌面端组件能快速搭建出专业的界面。数据库MySQL关系型数据库适合存储结构化的员工、部门、薪资等数据事务支持性好。权限控制Spring Security JWTSpring Security是Spring生态中强大的安全框架结合JSON Web Token (JWT)实现无状态的认证授权适合前后端分离架构。2. 开发环境与项目初始化“工欲善其事必先利其器”。在开始编码前请确保你的本地开发环境已就绪。以下是我开发时使用的环境版本你可以根据实际情况调整但建议大版本保持一致以避免兼容性问题。2.1 环境准备清单工具/环境推荐版本说明JDK1.8 或 11Spring Boot 2.x 对 JDK 8 兼容性最好Maven3.6用于后端项目依赖管理和构建Node.js14.x 或 16.x前端运行环境自带 npmMySQL5.7 或 8.0数据库服务器IDEIntelliJ IDEA / VS CodeIDEA用于后端Java开发VS Code用于前端Vue开发浏览器Chrome / Edge用于调试和测试前端页面2.2 创建数据库在MySQL中创建一个名为hrm_system的数据库字符集建议使用utf8mb4以支持完整的UTF-8字符如表情符号。CREATE DATABASE IF NOT EXISTS hrm_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2.3 初始化后端SpringBoot项目使用 Spring Initializr (https://start.spring.io/) 或 IDEA 自带的 Spring Initializr 功能创建项目。Project: Maven ProjectLanguage: JavaSpring Boot: 2.7.x (选择一个稳定版本)Project Metadata:Group:com.yourcompanyArtifact:hrm-backendPackaging: JarDependencies: 添加以下依赖Spring Web (用于构建Web API)Spring Security (安全框架)MyBatis Framework (或直接选 MyBatis Plus)MySQL Driver (数据库驱动)Lombok (简化Java Bean代码)JJWT (用于生成和解析JWT Token)创建完成后用IDE打开项目。2.4 初始化前端Vue项目打开命令行工具使用Vue CLI创建项目。如果你没有安装Vue CLI请先运行npm install -g vue/cli。# 创建一个新项目命名为 hrm-frontend vue create hrm-frontend在创建过程中手动选择特性Manually select features选择Babel,Router,Vuex,CSS Pre-processors。Vue版本选择2.x。路由模式选择Historymode。CSS预处理器选择Sass/SCSS。其他配置保持默认或根据喜好选择。创建完成后进入项目目录并安装Element UI。cd hrm-frontend vue add element # 在安装过程中选择“Import on demand”按需引入以优化打包体积。3. 后端核心设计与实现后端是整个系统的大脑负责业务逻辑处理、数据持久化和API提供。我们采用经典的分层架构Controller - Service - Mapper - Model。3.1 项目结构与依赖配置首先在后端项目的pom.xml中我们需要明确引入关键依赖特别是MyBatis-Plus和JWT。!-- pom.xml 部分依赖 -- dependencies !-- ... 其他Spring Initializr生成的依赖 ... -- !-- MyBatis-Plus 增强版ORM -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.2/version /dependency !-- JWT 支持 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies在application.yml或application.properties中配置数据库连接、JWT密钥等。# application.yml server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hrm_system?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: yourpassword jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 # 自定义JWT配置 jwt: tokenHeader: Authorization # 前端请求头中携带Token的字段名 secret: yourSecretKeyHere # 加密密钥务必复杂且保密 expiration: 604800 # Token有效期单位秒这里是一周 tokenHead: Bearer # Token前缀通常为Bearer 3.2 数据库表设计与实体类映射我们设计几个核心表用户表(sys_user)、部门表(sys_dept)、角色表(sys_role)、菜单/权限表(sys_menu)、员工信息表(emp_employee)、考勤记录表(att_attendance)、薪资记录表(sal_salary)。这里以用户表和员工表为例。-- 用户表 (关联系统登录) CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(64) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码, employee_id bigint(20) DEFAULT NULL COMMENT 关联的员工ID, role_id bigint(20) DEFAULT NULL COMMENT 角色ID, is_enabled tinyint(1) DEFAULT 1 COMMENT 账户是否启用 (1启用0禁用), create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_employee_id (employee_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 员工基本信息表 CREATE TABLE emp_employee ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 员工ID, name varchar(50) NOT NULL COMMENT 姓名, gender tinyint(1) DEFAULT NULL COMMENT 性别 (1男2女), id_card varchar(18) DEFAULT NULL COMMENT 身份证号, phone varchar(11) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 邮箱, dept_id bigint(20) DEFAULT NULL COMMENT 所属部门ID, position varchar(50) DEFAULT NULL COMMENT 职位, hire_date date DEFAULT NULL COMMENT 入职日期, work_status tinyint(1) DEFAULT 1 COMMENT 在职状态 (1在职2离职), create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_dept_id (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工信息表;使用MyBatis-Plus我们可以快速生成对应的实体类(Entity)。这里使用Lombok简化代码。// 文件路径src/main/java/com/yourcompany/hrm/entity/SysUser.java package com.yourcompany.hrm.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data TableName(sys_user) public class SysUser { TableId(type IdType.AUTO) private Long id; private String username; private String password; // 存储加密后的密码 private Long employeeId; private Long roleId; private Boolean isEnabled; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }3.3 核心业务认证授权与JWT集成这是前后端分离架构的安全基石。我们自定义一个JwtAuthenticationTokenFilter在每次请求前验证JWT Token。// 文件路径src/main/java/com/yourcompany/hrm/security/JwtAuthenticationTokenFilter.java Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { Autowired private UserDetailsService userDetailsService; Value(${jwt.tokenHeader}) private String tokenHeader; Value(${jwt.tokenHead}) private String tokenHead; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 1. 从请求头获取Authorization String authHeader request.getHeader(this.tokenHeader); // 2. 验证Header格式以Bearer开头 if (authHeader ! null authHeader.startsWith(this.tokenHead)) { // 3. 截取真正的Token String authToken authHeader.substring(this.tokenHead.length()); // 4. 从Token中解析出用户名 String username JwtTokenUtil.getUserNameFromToken(authToken); // 5. 如果用户名不为空且SecurityContext中尚未设置认证信息 if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { // 6. 根据用户名加载用户详情 UserDetails userDetails this.userDetailsService.loadUserByUsername(username); // 7. 验证Token是否有效未过期且用户名匹配 if (JwtTokenUtil.validateToken(authToken, userDetails)) { // 8. 创建认证通过的Token并设置到SecurityContext中 UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } // 9. 放行过滤器链 chain.doFilter(request, response); } }同时需要配置Spring Security放行登录接口、静态资源等。// 文件路径src/main/java/com/yourcompany/hrm/config/SecurityConfig.java Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabled true) // 启用方法级安全注解 public class SecurityConfig extends WebSecurityConfigurerAdapter { Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 禁用CSRF因为使用JWT无状态 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话 .and() .authorizeRequests() .antMatchers(/api/auth/login, /api/auth/register).permitAll() // 登录注册允许访问 .antMatchers(HttpMethod.GET, /, /*.html, /favicon.ico, /**/*.html, /**/*.css, /**/*.js, /swagger-resources/**, /v2/api-docs).permitAll() // 放行静态资源 .anyRequest().authenticated(); // 其他所有请求都需要认证 // 将JWT过滤器放在UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 禁用缓存 http.headers().cacheControl(); } // 密码编码器使用BCrypt强哈希加密 Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }3.4 员工管理模块实现示例以员工信息的增删改查CRUD为例展示标准的Controller-Service-Mapper结构。// 1. Controller层 (处理HTTP请求) // 文件路径src/main/java/com/yourcompany/hrm/controller/EmployeeController.java RestController RequestMapping(/api/employee) public class EmployeeController { Autowired private EmployeeService employeeService; GetMapping(/list) public Result listEmployees(RequestParam(required false) String name, RequestParam(required false) Long deptId, RequestParam Integer pageNum, RequestParam Integer pageSize) { PageEmployeeVO page employeeService.getEmployeeList(name, deptId, pageNum, pageSize); return Result.success(page); } PostMapping PreAuthorize(hasAuthority(emp:add)) // 方法级权限控制需要emp:add权限 public Result addEmployee(Valid RequestBody EmployeeDTO employeeDTO) { boolean saved employeeService.saveEmployee(employeeDTO); return saved ? Result.success(添加成功) : Result.failed(添加失败); } // 其他接口更新、删除、详情... } // 2. Service层 (实现业务逻辑) // 文件路径src/main/java/com/yourcompany/hrm/service/impl/EmployeeServiceImpl.java Service public class EmployeeServiceImpl extends ServiceImplEmployeeMapper, Employee implements EmployeeService { Override public PageEmployeeVO getEmployeeList(String name, Long deptId, Integer pageNum, Integer pageSize) { PageEmployee page new Page(pageNum, pageSize); LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), Employee::getName, name) .eq(deptId ! null, Employee::getDeptId, deptId) .orderByDesc(Employee::getCreateTime); this.page(page, wrapper); // 将Employee实体转换为EmployeeVO视图对象并填充部门名称等信息 return convertToPageVO(page); } Override public boolean saveEmployee(EmployeeDTO employeeDTO) { // 数据校验、业务逻辑处理如检查身份证号是否重复 Employee employee new Employee(); BeanUtils.copyProperties(employeeDTO, employee); // 设置其他默认字段... return this.save(employee); } } // 3. Mapper层 (由MyBatis-Plus自动代理无需写XML除非复杂SQL) // 文件路径src/main/java/com/yourcompany/hrm/mapper/EmployeeMapper.java Repository public interface EmployeeMapper extends BaseMapperEmployee { // 如需复杂联表查询可在此定义方法并在resources/mapper下编写对应的XML }4. 前端核心功能与页面实现前端负责数据的展示和用户交互。我们使用Vue Router管理路由Vuex管理全局状态如用户登录信息Axios进行网络请求。4.1 前端项目结构hrm-frontend ├── public ├── src │ ├── api # 所有后端API请求封装 │ ├── assets # 静态资源 │ ├── components # 公共组件 │ ├── layout # 布局组件如侧边栏、顶部导航 │ ├── router # 路由配置 │ ├── store # Vuex状态管理 │ ├── utils # 工具类如request.js, auth.js │ ├── views # 页面视图组件 │ │ ├── login # 登录页 │ │ ├── dashboard # 主页 │ │ ├── employee # 员工管理 │ │ ├── attendance # 考勤管理 │ │ └── ... │ ├── App.vue │ └── main.js └── package.json4.2 全局配置与请求拦截在src/utils/request.js中封装Axios实例统一处理请求拦截添加JWT Token和响应拦截处理通用错误。// src/utils/request.js import axios from axios import { Message } from element-ui import router from /router import store from /store // 创建axios实例 const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 从环境变量读取后端API地址 timeout: 5000 }) // 请求拦截器 service.interceptors.request.use( config { // 在发送请求之前如果存在token则将其添加到请求头 const token store.getters.token if (token) { config.headers[Authorization] Bearer token } return config }, error { console.log(error) return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response { const res response.data // 假设后端统一返回格式为 { code: 200, message: success, data: ... } if (res.code ! 200) { Message({ message: res.message || Error, type: error, duration: 5 * 1000 }) // 如果是401未授权则跳转到登录页 if (res.code 401) { store.dispatch(user/resetToken).then(() { router.push(/login?redirect${router.currentRoute.fullPath}) }) } return Promise.reject(new Error(res.message || Error)) } else { return res } }, error { console.log(err error) Message({ message: error.message || 网络错误, type: error, duration: 5 * 1000 }) return Promise.reject(error) } ) export default service4.3 员工管理页面示例这是一个典型的列表查询页面包含搜索、表格、分页和操作按钮。!-- src/views/employee/index.vue -- template div classapp-container div classfilter-container el-input v-modellistQuery.name placeholder员工姓名 stylewidth: 200px; keyup.enter.nativehandleFilter / el-select v-modellistQuery.deptId placeholder所属部门 clearable stylewidth: 200px; el-option v-foritem in departmentOptions :keyitem.id :labelitem.name :valueitem.id / /el-select el-button typeprimary iconel-icon-search clickhandleFilter搜索/el-button el-button typesuccess iconel-icon-plus clickhandleCreate新增员工/el-button /div el-table v-loadinglistLoading :datalist border fit highlight-current-row stylewidth: 100%; el-table-column labelID propid aligncenter width80 / el-table-column label姓名 propname aligncenter / el-table-column label性别 aligncenter template slot-scope{row} {{ row.gender | genderFilter }} /template /el-table-column el-table-column label部门 propdeptName aligncenter / el-table-column label职位 propposition aligncenter / el-table-column label入职日期 prophireDate aligncenter width120 / el-table-column label操作 aligncenter width230 class-namesmall-padding fixed-width template slot-scope{row} el-button typeprimary sizemini clickhandleUpdate(row)编辑/el-button el-button sizemini typeinfo clickhandleView(row)详情/el-button el-button v-ifrow.status!deleted sizemini typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table pagination v-showtotal0 :totaltotal :page.synclistQuery.page :limit.synclistQuery.limit paginationgetList / /div /template script import { fetchEmployeeList, deleteEmployee } from /api/employee import Pagination from /components/Pagination // 封装的分页组件 export default { name: EmployeeList, components: { Pagination }, filters: { genderFilter(gender) { const genderMap { 1: 男, 2: 女 } return genderMap[gender] } }, data() { return { list: null, total: 0, listLoading: true, listQuery: { page: 1, limit: 10, name: undefined, deptId: undefined }, departmentOptions: [] // 从store或api获取 } }, created() { this.getList() this.getDepartments() }, methods: { getList() { this.listLoading true fetchEmployeeList(this.listQuery).then(response { this.list response.data.records this.total response.data.total this.listLoading false }) }, getDepartments() { // 调用获取部门列表的API }, handleFilter() { this.listQuery.page 1 this.getList() }, handleCreate() { this.$router.push(/employee/add) }, handleUpdate(row) { this.$router.push(/employee/edit/${row.id}) }, handleDelete(row) { this.$confirm(确认删除该员工记录, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { deleteEmployee(row.id).then(() { this.$message({ type: success, message: 删除成功! }) this.getList() }) }) } } } /script5. 前后端联调与系统运行5.1 启动后端服务确保MySQL服务已启动且hrm_system数据库已创建。在IDE中运行HrmBackendApplication主类或使用Maven命令mvn spring-boot:run。观察控制台日志确认无报错并看到类似Tomcat started on port(s): 8080的信息。5.2 启动前端服务在hrm-frontend目录下创建.env.development文件配置后端API地址。VUE_APP_BASE_API http://localhost:8080运行npm run serve。浏览器访问http://localhost:8081(默认前端端口)看到登录页。5.3 核心流程测试登录使用默认管理员账号需要在后端初始化或通过SQL插入登录。员工管理进入员工管理页面尝试新增、查询、编辑、删除员工。权限验证尝试在浏览器控制台手动修改一个普通员工的Token访问管理员API观察后端是否返回403禁止访问。数据一致性在员工管理页删除一个员工检查数据库对应记录是否被正确标记或删除。6. 常见问题与排查思路在开发和部署过程中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案前端页面访问后端API报4041. 后端服务未启动。2. 后端API路径写错。3. 跨域问题。1. 检查后端控制台是否启动成功。2. 核对前端request.js中的baseURL和后端RequestMapping路径。3. 在后端添加跨域配置CORS。登录成功但后续请求报401/4031. Token未正确携带。2. Token已过期。3. 用户权限不足。1. 检查浏览器开发者工具Network面板请求头是否包含Authorization: Bearer xxx。2. 检查JWT有效期配置重新登录获取新Token。3. 检查该用户角色和接口所需的权限注解是否匹配。插入数据时前端显示成功但数据库无记录1. 后端事务未提交。2. 数据库字段不匹配如驼峰转下划线。3. MyBatis-Plus的字段填充策略未生效。1. 检查Service方法是否有Transactional注解或手动提交。2. 检查实体类字段名与数据库列名映射关系可使用TableField注解指定。3. 检查MetaObjectHandler配置是否正确。页面样式错乱或Element UI组件不显示1. Element UI未正确引入或按需引入配置错误。2. CSS文件未加载。1. 检查main.js或插件中Element UI的引入方式。2. 检查浏览器控制台是否有CSS资源加载错误。分页查询结果不正确1. 前端传递的分页参数名与后端接收的不一致。2. 后端分页逻辑有误如未计算总条数。1. 统一前后端参数名如pageNum/page,pageSize/limit。2. 确保MyBatis-Plus的Page对象被正确使用且查询wrapper未影响分页。7. 项目优化与扩展建议一个可用的系统是基础一个健壮、易维护的系统才是目标。以下是一些优化和扩展方向7.1 后端优化建议API文档化集成Swagger或Knife4j自动生成在线API文档方便前后端协作和测试。统一响应封装所有Controller返回Result对象包含code、message、data便于前端统一处理。全局异常处理使用ControllerAdvice和ExceptionHandler捕获并处理各类异常返回友好的错误信息而不是堆栈跟踪。数据缓存对频繁读取且变化不频繁的数据如部门列表、权限菜单使用Redis进行缓存减轻数据库压力。操作日志使用AOP或过滤器记录关键操作如增删改的日志便于审计和问题追溯。数据权限在RBAC基础上增加数据行级权限控制如部门经理只能看本部门员工。7.2 前端优化建议路由懒加载将不同功能模块的路由组件按需加载提升首屏加载速度。// 在router/index.js中 const EmployeeList () import(/views/employee/index)API模块化管理将不同模块的API请求函数分别放在src/api目录下的不同文件中如employee.js、attendance.js便于维护。组件化抽象将重复使用的UI逻辑如复杂的表单、弹窗封装成独立的Vue组件。状态管理细化合理规划Vuex中存储的数据避免将所有状态都塞进一个store。可以按模块拆分。7.3 功能扩展方向考勤模块集成打卡机数据接口或开发移动端GPS/Wi-Fi打卡功能自动生成考勤统计报表。薪资模块设计灵活的薪资项和计算公式支持社保公积金自动计算生成电子工资条。审批流引擎集成工作流引擎如Flowable、Activiti实现请假、报销、加班等流程的自动化流转与审批。报表分析使用ECharts等图表库可视化展示人员结构、离职率、考勤异常等数据分析看板。消息通知集成邮件、企业微信、钉钉等通知渠道发送入职欢迎、生日祝福、审批提醒等消息。这套基于SpringBootVue的人事管理系统从技术选型到模块设计都遵循了当前主流的开发实践。它不仅是一个可运行的毕业设计项目更是一个包含了权限管理、前后端分离、RESTful API设计等核心要素的实战模板。你可以在此基础上根据实际业务需求进行深度定制和扩展。
返回列表