ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot + Vue 人力资源管理系统源码解析与本地部署实战

Spring Boot + Vue 人力资源管理系统源码解析与本地部署实战 简介这是一套面向计算机专业毕业生与Java学习者的前后端分离人力资源管理系统项目。基于Spring Boot构建后端接口搭配Vue与ElementUI实现管理端界面涵盖员工信息、考勤、薪资等常见模块可用作毕业设计、课程设计或期末大作业的完整参考。压缩包共178个文件约3.49MB以Java源码、Vue组件、JavaScript脚本、SQL数据库脚本及项目说明文档为主同时包含Maven配置、环境变量模板与PDF说明材料目录结构清晰便于直接导入开发工具运行调试。目前已有805人学习项目曾获老师指导并以高分通过对理解企业级HRM业务流程和前后端交互方式有较大帮助。读者可从中获取完整可运行源码、初始化数据库脚本、项目结构说明文档以及基于ElementUI的页面实现思路适合需要快速搭建同类型管理系统或进行二次开发的场景。1. 拆开这份人力资源管理系统源码之前先想一个问题Spring Boot Vue ElementUI 组合的人力资源管理系统源码被问到的频率非常高中小软件团队接单要交付企业内部想自己维护个人开发者想拿一套完整工程做二次开发。这套组合之所以普及是因为后端有 Spring Boot 负责接口、权限和数据库操作前端有 Vue ElementUI 快速出管理后台界面两边都能找到大量成熟实践不像某些重型商业框架那样绑定学习成本。真正的问题不是你懂不懂 Java 和 Vue而是拿到一个 zip 包后怎么在最短时间内把数据库、后端、前端三个部分串起来并判断这套系统适不适合你的业务。2. 拆开压缩包拿到源码之后先看哪四样东西2.1 前端工程Vue 管理后台的典型目录结构解压之后前端部分通常是一个独立的 Vue 项目。你会看到src、public、package.json、vue.config.js这类文件。src下面一般按照api、views、router、store、utils拆开这套组织方式在开源管理后台里已经形成事实标准几乎不需要适应成本。frontend ├─ public │ └─ index.html ├─ src │ ├─ api │ ├─ assets │ ├─ components │ ├─ router │ ├─ store │ ├─ utils │ ├─ views │ ├─ App.vue │ └─ main.js ├─ package.json └─ vue.config.js我一般打开工程之后先看package.json里的scripts和dependencies。前者能确认开发命令是npm run dev还是npm run serve后者能看出来用的是 ElementUI 还是 Element Plus这决定了组件写法和 Vue 版本匹配关系。标题写的是 ElementUI那大概率落在 Vue 2 生态如果你本机装的是 Vue 3 的全局环境不要急着改代码先确认依赖版本再动手。src/api目录值得花时间过一遍这里通常按业务模块封装了所有请求employee.js、attendance.js、salary.js。看完这些文件你对前端调了哪些接口就有了整体印象后面联调时定位问题会快很多。2.2 后端工程Spring Boot 三件套一找就懂后端工程一般是一个 Maven 或 Gradle 项目拿到手先找pom.xml确认 Spring Boot 版本、MyBatis 版本、是否引入 Redis、是否引入 JWT 和 Spring Security。然后在src/main/java下按包名扫一遍常见做法是按controller、service、mapper、entity分层少数项目会再加一层config和common。backend ├─ src/main/java │ └─ com/company/hrm │ ├─ controller │ ├─ service │ ├─ mapper │ ├─ entity │ ├─ config │ └─ common ├─ src/main/resources │ ├─ mapper │ ├─ application.yml │ └─ static ├─ pom.xml └─ sqlresources/mapper下存放 MyBatis 的 XML 文件如果你常常要和数据库打交道这一层基本是查阅频率最高的地方。每个 XML 文件对应一张表或一类查询SQL 写得规不规范、有没有resultMap、分页是不是写在 XML 里都直接影响后续改动的风险。很多新人习惯把所有查询写在 Service 里用注解完成但成熟的交付项目更倾向 XML 集中管理复杂 SQL这个选择本身没有对错你只需要先顺着项目的惯例走。application.yml是启动入口的关键数据源、Redis、日志、文件上传目录、token 密钥都在这里。拿到代码第一件事就是把这个文件里数据库账号、密码、IP 改成你自己的环境再考虑启动问题。2.3 SQL 脚本先理清权限表和业务表的关系人力资源系统的数据库脚本是这套源码里最值钱也最容易出状况的部分。常见做法是在sql目录下放一个数据库脚本或按升级版本拆成多份。脚本里面大体包含两类表。一类是系统表负责权限和基础配置用户表、部门表、角色表、菜单表、用户角色关联表、角色菜单关联表。另一类是业务表员工表、考勤表、薪资表、请假表、招聘表。两类表之间本身没有直接外键业务表都通过dept_id、emp_id这样的逻辑外键关联。表名用途关键字段sys_user登录用户id、username、password、dept_id、statussys_role角色id、role_name、role_keysys_menu菜单权限id、menu_name、parent_id、path、permsemployee员工档案id、emp_no、name、dept_id、position_id、entry_dateattendance考勤记录id、emp_id、work_date、check_in、check_out、statussalary薪资记录id、emp_id、base_salary、bonus、deductions、salary_dateleave请假申请id、emp_id、leave_type、start_time、end_time、status拿到 SQL 文件之后先不要急着导入先打开随便看一眼表名的命名风格。是sys_前缀还是t_前缀是统一下划线还是驼峰会影响你后面写 Mapper 时的字段映射。另外注意建表语句里有没有DROP TABLE IF EXISTS如果你在已有数据的库上执行这个语句会直接把旧表清掉。我通常会把DROP那段先注释再执行算是给自己留一道防火墙。2.4 项目说明与部署文档先读哪个更省时间压缩包里既然写了“项目说明文档”说明交付方是希望你能独立把系统跑起来的。拿到手之后我会按这个顺序读先看部署文档再看项目说明最后看数据库文档。部署文档解决的是“怎么跑起来”里面通常有环境要求、数据库导入步骤、后端启动参数、前端启动命令。这些内容直接决定你在 IDE 里的操作顺序。项目说明解决的是“这套系统做了什么”包括功能清单、角色划分、页面截图这部分能帮助判断系统是否符合你客户的需求。数据库文档解决的是“表和表之间怎么关联”一般包含 ER 图和字段解释二次开发时查询字段含义翻数据库文档比翻代码快得多。有一个容易被忽略的动作拿解压时间。zip 包里有前后端工程和文档先看一眼文件修改时间能大概判断这套代码是不是统一交付的。如果前端代码和后端代码时间差半年要小心两边的接口未必完全对得上这可能成为你联调时最大的坑。3. 在本地把工程跑起来环境版本与最小启动步骤3.1 先准备一套不折腾的环境组合这套技术栈对版本敏感尤其是 Spring Boot、MyBatis、Node.js 三者的配合。常见配置是 JDK 1.8 Maven 3.6 MySQL 5.7 Node.js 14这套组合在大量交付项目里被验证过虽然不是最新但兼容性最稳。组件推荐版本说明JDK1.8多数 Spring Boot 2.x 工程的默认基线MySQL5.7 / 8.0注意 8.0 需要调整驱动和时区Maven3.6依赖下载慢的问题配置国内镜像Node.js12 或 14对应 Vue 2 ElementUI不易出依赖问题Redis5.x如果工程用了验证码或缓存必须启动这里要提醒一句如果你本机装的是 JDK 17 或 Node.js 20不建议一上来就用。JDK 17 下很多 Spring Boot 2.3 版本的项目会出现反射和模块访问报错Node.js 20 第一次执行 npm install 时可能因为依赖版本太老报警告或编译失败。先用这套老版本组合跑通再考虑升级这才是成熟做法。3.2 改后端配置启动 Spring Boot 服务后端启动前必改三个地方。第一是 MySQL 连接第二是 Redis 连接第三是文件上传或日志路径。如果你是本地运行把 IP 改成localhost账号密码改成你的 MySQL 账号即可。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hrm?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.company.hrm.entity这段配置里有几个参数需要明确serverTimezoneAsia/Shanghai解决 MySQL 8.0 时区报错max-file-size控制上传文件上限人力资源系统经常导入 Excel 员工花名册默认的 1MB 往往不够用mapper-locations指定 MyBatis XML 所在位置如果这个路径写错启动不会报错但你一调用接口就提示Invalid bound statement。type-aliases-package是给 XML 里的返回值类型起别名的配置了之后XML 里可以用实体类短名不用写全限定名。启动后端直接在 IDEA 里运行Application主类。看到Started Application in xx seconds就算成功。常见的失败是端口被占用、Redis 没启动、数据库连不上。端口占用用netstat -ano | findstr 8080查进程Redis 没启动时报的是连接拒绝而不是什么深奥的异常看到这类错误先检查 Redis 服务。3.3 导入数据库命令行与可视化工具二选一多数交付文档里会写明数据库名一般叫hrm或hrms。如果文档没写打开 SQL 文件第一行看CREATE DATABASE这是最可靠的方式。导入方式我推荐先建库再执行脚本避免用工具图形向导一把梭时字符集出错。mysql -u root -p CREATE DATABASE hrm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hrm; SET NAMES utf8mb4; SOURCE /path/to/hrm.sql;执行完确认关键表数量不要直接启动。用SHOW TABLES;看一眼有没有员工表、权限表。如果表数量与文档描述相差太多大概率是脚本中间报错中断了。手动执行时的SET NAMES utf8mb4是为了让导入过程使用的连接字符集和库字符集一致不然中文注释和初始化的管理员账号名称会变成乱码后面你登录看到的将是????。如果你习惯用 Navicat 这类工具要注意工具本身的连接字符集设置。新建连接时把编码选成utf8mb4再用“运行 SQL 文件”导入。不要直接在查询窗口里复制粘贴整个文件遇到包含自定义分隔符的存储过程或触发器会截断。3.4 前端依赖安装与启动开发服务前端这一步最容易翻车的就是依赖安装超时或失败。npm 默认下载源在国外公司内网或家庭网络拉取 Vue 相关组件时会出现各种ETIMEDOUT。我一般先把 npm 源切换成国内镜像再安装这一步能省掉大量无意义的重试。npm config set registry https://registry.npmmirror.com npm install npm run devsrc/api下的请求封装通常会把接口前缀写成一个常量例如/api但很多后端工程并没有配置context-path接口路径是直接以/employee开头的。这时就需要在开发服务器里做一层代理把/api前缀转发到后端端口并去掉前缀。module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }这里的port是前端页面地址target是后端地址pathRewrite负责把/api去掉。多数工程已经预置好了这套配置你只需要检查target的端口是不是与后端server.port一直。如果target是 8090 而后端实际跑在 8080登录时就会看到请求能发出去但一直停滞或返回 404。前端启动后浏览器访问http://localhost:3000看到登录页就说明前端静态部分已经正常。3.5 默认账号、验证码与登录链路登录验证是联调前的最后一道关卡。人力资源系统的默认账号通常是admin密码可能是admin123或123456文档里一般会写不要自己盲猜。大多数这类工程的登录流程是这样前端拿到用户名、密码、验证码一起 POST 到/login后端校验成功后返回一个 token前端把 token 存到 localStorage 或 Vuex之后每个请求在 axios 拦截器里自动带上Authorization头。如果工程接了 Redis验证码的值会存在 Redis 里过期时间一般 2 分钟。你在前端看到“验证码已过期”的提示说明后端和 Redis 连接正常流程是通的如果验证码一直不刷新反而要怀疑后端接口报错。登录成功后跳转到首页显示菜单栏目这套系统的核心功能已经可用。再从源码里导入几条测试员工数据验证列表页能不能正常分页这就是一次完整的最小启动验证。4. 核心模块拆解从数据表到 Vue 页面的完整链路4.1 员工管理一张表撑起整个信息维护页面员工管理是人力资源系统的核心模块也是二次开发经常改的地方。先看表结构它决定了页面上能展示什么字段。CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 1 COMMENT 性别 1男 2女, dept_id INT COMMENT 部门ID, position_id INT COMMENT 岗位ID, phone VARCHAR(20), email VARCHAR(100), education VARCHAR(20) COMMENT 学历, entry_date DATE COMMENT 入职日期, status TINYINT DEFAULT 1 COMMENT 状态 1在职 0离职, create_time DATETIME, KEY idx_dept (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计里有几个细节值得注意。emp_no加唯一约束避免重复工号dept_id和position_id不直接存部门名称和岗位名称而是存 ID页面展示时通过关联查询补全名称。这样设计的好处是部门改名后不用批量更新员工数据坏处是列表查询一定要带关联否则页面上部门列全是 ID。项目经理和员工页面就是靠这两张字典表接起来的。前端列表页对应的是一个 ElementUI 页面一般包含搜索表单、数据表格、分页组件和新增编辑弹窗。核心表格部分长这样el-table :datatableData v-loadingloading border stripe el-table-column propempNo label工号 width100 / el-table-column propname label姓名 width120 / el-table-column propdeptName label部门 width140 / el-table-column proppositionName label岗位 width140 / el-table-column propphone label手机号 / el-table-column propentryDate label入职日期 / el-table-column label操作 width160 template #default{ row } el-button typetext clickedit(row)编辑/el-button el-button typetext classdanger clickremove(row)删除/el-button /template /el-table-column /el-tableprop必须与后端返回的字段名完全一致后端如果返回empNo而前端写emp_no表格这一列就是空白。这是前后端联调最常见的问题排查时直接看接口返回的 JSON 字段名不要凭记忆猜。deptName这种字段在表里不存在它来自后端关联查询的结果。如果你二次开发想新增一个“籍贯”字段需要同时改四层数据库表、实体类、Mapper 查询、前端表格列少一层都显示不出来。4.2 考勤与薪资两个高频报表的数据链路考勤和薪资是人力资源系统里最容易出现计算偏差的模块。考勤表结构通常长这样CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, work_date DATE NOT NULL, check_in TIME, check_out TIME, status TINYINT COMMENT 0迟到 1正常 2缺勤 3加班, UNIQUE KEY uk_emp_date (emp_id, work_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里最关键的是UNIQUE KEY同一员工同一天只有一条记录。如果去掉这个约束打卡数据重复导入两次就会出现同一天多条考勤薪资汇总时这部分员工会被重复计薪。这种隐患很难从页面看出来只有在月底核对工资时才会被发现而且那时候数据已经错了整个月。薪资表的计算逻辑一般放在后端 Service 层。常见流程是读取考勤记录统计正常出勤天数、迟到次数、缺勤天数再结合基本工资、绩效工资、社保扣除得出实发工资。源码里如果这块逻辑不复杂通常会写成一个calculateSalary(empId, month)方法入参是员工 ID 和月份返回值是包含各项明细的工资对象。做薪资模块二次开发时不建议改表结构优先改计算规则。因为薪资表字段一旦变更历史月份的工资数据就会失去可比性。更好的做法是增加一个新字段保留原始数据例如base_salary_origin计算调整后对照差异。4.3 权限设计JWT 身份校验与 Vue 路由守卫人力资源系统里员工、部门主管、HR、管理员看到的菜单和数据范围是不同的权限控制是这类系统的刚需。这套源码常见的做法是 Spring Security JWT后端保护接口前端保护页面。JWT 过滤器的核心逻辑是每次请求从 Header 里取出 token验签通过就放行否则返回 401。Component public class JwtFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token.substring(7)) .getBody(); request.setAttribute(claims, claims); } chain.doFilter(request, response); } }secretKey是签名密钥在配置文件中维护不要硬编码在 Java 代码里。parseClaimsJws会校验 token 是否过期和签名是否有效捕获到异常时常见做法是抛出一个未登录异常由全局异常处理器转成 401 响应。这里有个容易被忽略的细节JWT 是无状态的如果修改了密码或禁用了一个账号旧的 token 在过期前仍然有效。所以你在做“强制下线”功能时需要在内存或 Redis 里维护一个 token 黑名单或者把 token 版本号写进 Redis。前端路由守卫保护页面router.beforeEach((to, from, next) { const token getToken() if (to.meta.requiresAuth !token) { next(/login) } else { next() } })这只是拦截未登录用户并不能拦截权限不足的用户。更完整的做法是在登录之后根据当前用户的菜单权限动态生成路由表Vue Router 的addRoutes方法专门干这个。源码里如果已经实现了动态路由菜单列表一般来自/getUserMenus接口前端根据接口返回的菜单树渲染侧边栏。改菜单名称只需要在数据库sys_menu表改一条记录不需要重新编译前端这就是动态路由带来的好处。5. 联调避坑前后端白屏、乱码、分页异常的五条记录5.1 前端登录页白屏或接口 404现象是浏览器能打开登录页但点登录按钮后无响应控制台显示请求 404 或有跨域报错。原因分成两类。第一类是接口路径对不上。后端可能配置了context-path: /api那后端实际路径是/api/employee/page而前端代理里又包了一层/api前缀相当于请求变成了/api/api/employee/page。这类路径叠加问题是联调阶段出现频率最高的。第二类是代理目标端口配错target指向了 8081但后端跑在 8080请求直接打到不存在的服务上。解决方式是先打开浏览器 Network 面板看实际发出的完整 URL再和后端 Controller 的RequestMapping映射对比。确认路径规则后统一前后端的服务路径约定。我自己的习惯是后端不用context-path所有接口直接以模块名开头前端代理只补一层前缀这样路径最直观也最不容易产生叠加。5.2 数据库导入后中文乱码现象是 MySQL 表里的注释、初始化菜单名称、管理员姓名都变成了问号或乱码。原因有两层SQL 文件本身保存的是 UTF-8 编码但连接 MySQL 时用的字符集可能是latin1或建库时没有指定utf8mb4用了默认编码。字符集一旦写进表定义后期再改非常麻烦。解决方式在建库时就定死。CREATE DATABASE明确指定utf8mb4然后导入前执行SET NAMES utf8mb4。如果已经导入错了先把表结构删掉重新导入不要试图只改表的 character set那只会让已存入的乱码继续存在。另外一个易漏点如果 SQL 文件里有SET FOREIGN_KEY_CHECKS0;这是在提示你表之间有外键关系导入时短暂关闭外键校验是正常的但导入完成后要确保外键约束都有效。5.3 员工列表时间字段显示一串数字现象是表格里入职日期显示成1588176000000这样的数字。原因是后端返回的 JavaDate类型被 Jackson 默认序列化成了时间戳前端拿到的是毫秒值而 ElementUI 表格不会自动把它格式化成日期字符串。这个问题在新老团队协作时属于高频摩擦前端认为后端该格式化后端认为前端该解析。解决方式有两条。后端在日期字段上加JsonFormat(pattern yyyy-MM-dd, timezone GMT8)控制接口返回字符串或者前端拿到Date类型值后自己格式化。我更推荐后端统一格式化因为同一份数据可能被 PC 页面、Excel 导出、报表模块共用让后端保证输出稳定前端只管消费结果。注意时区要写成GMT8否则差 8 小时日期会少一天。5.4 PageHelper 分页查询 total 不变或数据重复现象是第一页数据正常切到第二页返回的数据和第一页一样或者总条数一直是 0 或保持初始值不变。原因大多是 PageHelper 分页插件和 MyBatis 的集成配置不对。常见情况是PageHelper不是全局拦截器方式生效而是手动在代码里startPage之后执行查询但紧接着又执行了一条不需要分页的 SQL分页参数被那次查询消耗掉。另一种可能是分页时直接改写了原来的查询 SQL导致 count 语句统计不出来。解决方式是遵守 PageHelper 的使用约定startPage之后必须紧跟第一条查询语句中间不要穿插其他 SQL 操作。代码写成PageHelper.startPage(pageNum, pageSize); ListEmployee list employeeMapper.selectPage(query); PageInfoEmployee pageInfo new PageInfo(list);PageInfo.getTotal()才是正确总数。另外检查 Maven 依赖里是不是重复引入了多个 MyBatis 分页插件两个分页插件会互相抢占表现就是分页时灵时不灵纯玄学。删除多余的依赖保留一个版本即可。5.5 上传员工照片一直失败现象是新增员工时上传头像页面提示文件上传失败后端日志里能看到FileSizeLimitExceededException。原因很直接spring.servlet.multipart的默认上限是 1MB而现在的手机照片普遍 3MB 以上一传就超限。类似问题还会出现在 Excel 批量导入员工数据时几万行数据的文件轻松超过 10MB。解决方式是把application.yml里的上传上限调大同时在前端el-upload组件上同步设置limit和accept。如果系统部署在 nginx 后面还要确认 nginx 的client_max_body_size也调大否则即使后端放开了限制请求也到不了后端就被 nginx 拦截。这三层限制要一起改排查时从外到内逐层确认才不用反复做无意义的重启。6. 打包上线把 Vue 构建产物交给 Spring Boot 统一托管本地跑通之后最稳妥的上线方式是前端构建成静态文件放进后端src/main/resources/static再用 Maven 打成一个 jar。这样做的好处是部署时只需维护一个进程、一个端口不需要单独配置 nginx 转发规则对小型团队比较友好。先修改前端构建配置确保静态资源路径是相对路径module.exports { publicPath: ./, outputDir: dist, productionSourceMap: false, assetsDir: static }publicPath: ./解决部署在非根路径时资源 404 的问题。assetsDir指定静态资源子目录让打包后的文件结构清晰。然后执行npm run build把生成的dist目录里的文件复制到后端的src/main/resources/static重新打包后端 jar。如果后端static已经存在同名文件先删干净再复制避免旧文件残留造成路由冲突。生产环境配置要单独处理推荐用application-prod.yml外置启动命令里通过--spring.profiles.activeprod激活java -jar hrm.jar --spring.profiles.activeprod --spring.config.location/opt/hrm/config/这样数据库密码、上传目录这些环境相关的配置不用打回代码里运维人员直接改外置文件即可。上线前按下面这张表逐项检查能少很多半夜被叫起来的次数。检查项操作方式预期结果登录接口curl -X POST http://ip:8080/login返回 JSON 与 token静态页面浏览器访问http://ip:8080/出现登录页文件上传上传 5MB 以上图片文件落盘成功数据库备份用 mysqldump 触发一次备份备份文件大小正常日志输出查看/opt/hrm/logs无 ERROR 堆栈我个人的经验是上线第一周别急着删/logs目录遇到问题第一件事就是看当天日志里有没有SQLException和NullPointerException。人力资源系统的数据敏感度高上线前记得把默认密码强制改成随机密码把 Spring Boot 的/actuator对外访问关掉数据库不要直接用 root 账号连业务库。这套组合能跑起来只是起点能在团队手里稳定维护才是这套源码真正值钱的地方。希望这篇记录能帮你少折腾几个晚上把精力花在业务本身的打磨上。本文还有配套的精品资源点击获取
返回列表