简介:面向 Java 毕设与人事管理系统开发者的完整交付包,基于 Spring Boot 集成 IOC、MVC、ORM 及 Spring Security 安全框架,前端采用 Vue.js 与 Element UI,按 MVVM 模式实现前后端分离的单页应用,覆盖在线通信、员工资料、人事信息管理、薪资调整分配、统计分析和系统设置等模块,另含完整设计论文与答辩 PPT。压缩包共 319 个文件、约 92.56MB,其中 126 个 Java 文件承载后端逻辑与实体映射,js/css 等前端资源对应 Vue 组件和构建产物,sql 为数据库初始化脚本,另有论文文档、答辩 PPT 及 Maven 打包配置和 Dockerfile,目录分层清晰,便于直接导入运行与二次开发。目前已有 2294 人学习下载。读者可获得可落地的项目源代码、数据库初始化脚本、设计论文与答辩演示材料,能快速梳理人事管理系统需求、权限模型与前后端交互流程,适合需要准备毕设答辩或扩展课设功能的中高级 Java 学习者。
1. 用 Spring Boot 做人事管理系统,先想清楚这三点再动手
人事管理系统(HRMS)大概是 Spring Boot 毕设和内部项目中出现频率最高的题目之一,但多数实现停留在“增删改查演示”层面:员工表、部门表、登录退出,代码跑通了,论文没东西写。真正能在答辩现场站得住的设计,通常是把组织架构、员工全生命周期、考勤薪酬核算这三条主线理清楚,再配合 Spring Boot 的四层架构规范落地。本文按“理论 → 表结构 → 核心代码 → 调试验收”的顺序,给你一条能直接照着实现的技术路线,附带可以直接用的脚本和关键代码片段。无论你是做毕设、写论文,还是给公司搭内部人事后台,这套方案都适用;对于有经验的后端工程师,文中的权限模型和部门树递归处理也有参考价值。
2. Spring Boot 人事系统的四层架构与核心表设计
2.1 为什么人事系统特别适合用四层架构
Spring Boot 项目最常见的组织方式就是四层架构:Controller 层负责接收请求和参数校验,Service 层处理业务规则,Mapper(或 Repository)层做数据持久化,再加上统一的 Model/VO 层传递数据。这个分层方式对人事系统尤其合适,因为人事系统的业务边界非常清晰:组织管理、员工管理、考勤、薪酬、招聘、培训,每个模块的 Service 都能独立迭代,不会互相纠缠。
在具体技术选型上,持久层有两个选择:MyBatis-Plus 和 Spring Data JPA。做人事管理系统,我一般推荐 MyBatis-Plus,理由很实际:
- 内置分页插件,员工列表这种典型的分页查询场景不用手写 PageHelper 配置
- 逻辑删除支持好,员工离职记录可以保留历史数据,论文里“数据安全性”论证有素材
- 代码生成器能一次性生成 entity、mapper、service、controller,省掉大量重复劳动
四层架构在 Spring Boot 中的体现,就是包结构必须清晰。参考下面的目录规范:
com.example.hrms ├── controller # REST 接口层 │ ├── EmployeeController.java │ └── DepartmentController.java ├── service # 业务逻辑层 │ ├── impl │ └── EmployeeService.java ├── mapper # 数据访问层 │ └── EmployeeMapper.java ├── entity # 数据库映射实体 ├── dto # 入参/出参对象 ├── vo # 视图对象 ├── config # 配置类(拦截器、跨域等) ├── common # 统一返回结果、异常处理、工具类 └── HrmsApplication.java2.2 员工表、部门表、薪资表的数据模型设计
表结构是论文里“数据库设计”章节的核心材料,必须能体现业务深度,见下表:
| 表名 | 关键字段 | 用途说明 |
|---|---|---|
| department | id, parent_id, dept_name, leader_id, sort_order | 树形组织架构,parent_id 指向父部门 |
| employee | id, emp_no, name, gender, dept_id, position, hire_date, status | 员工主表,status 区分在职/离职/试用 |
| salary | id, emp_id, base_salary, bonus, deduction, month | 月度薪资记录,一个员工多条,按月份查询 |
| attendance | id, emp_id, work_date, check_in, check_out, status | 考勤明细,status 标记正常/迟到/缺勤 |
| user | id, username, password, role_id, emp_id | 系统登录账号 |
其中员工表和部门表的关系最头疼:一个部门有多个员工,一个员工归属一个部门,但在组织架构调整时,员工需要批量调部门。这个场景在设计上需要把 dept_id 作为普通索引字段放在 employee 表中,而不建外键约束。原因是人事系统里离职员工的数据要长期保留,外键的级联删除会让历史数据丢失,逻辑删除优于物理删除。
组织架构的树形结构是另一个关键点。部门表通过 parent_id 实现无限层级,查询子树时如果只用 SQL 递归,在 MySQL 8.0 以下版本会比较麻烦。其中一种实用的方案是:在代码里递归组装树,数据量小时简单直接,见下面的示例:
public List<DepartmentVO> buildDeptTree(List<Department> allDepts, Long parentId) { return allDepts.stream() .filter(dept -> parentId.equals(dept.getParentId())) .map(dept -> { DepartmentVO vo = new DepartmentVO(); vo.setId(dept.getId()); vo.setDeptName(dept.getDeptName()); vo.setChildren(buildDeptTree(allDepts, dept.getId())); return vo; }).collect(Collectors.toList()); }这段代码的逻辑是:每次递归都从全量部门列表中筛选出 parentId 匹配的子部门,再对每个子部门递归调用自身,直到没有子节点为止。需要注意的是,这个方法适合部门数量在几百量级的系统;如果部门过万,建议改用路径枚举法,在表里增加 dept_path 字段存储父级路径,以空间换查询效率。
2.3 统一返回结果与全局异常处理的规范写法
人事管理系统的接口数量通常在 30 个以上,如果不做统一封装,Controller 里全是散落的 Map 或直接返回实体,前端联调时每个接口的响应格式都不一样。这个问题的标准解法是定义 Result 类和全局异常处理器。
为了方便论文答辩时讲解,我把统一返回类的核心代码整理如下:
@Data public class Result<T> { private Integer code; // 200 成功,非 200 失败 private String message; // 提示信息 private T data; // 业务数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }使用这个返回类之后,Controller 里的写法就是return Result.success(employeeService.getEmployeeById(id));,前端可以根据 code 字段判断请求结果,message 直接用于提示。
全局异常处理要配套实现,主要通过 @RestControllerAdvice 注解完成。作用是:在业务代码中抛出异常时,不会被 Spring Boot 默认的错误页拦截,而是统一走自定义的异常处理逻辑,数据访问异常、参数校验异常都能映射为规范的响应体。这在论文中可以作为“系统健壮性设计”的论据。
3. 员工信息管理模块的实现与参数配置要点
3.1 Spring Boot + MyBatis-Plus 实现员工增删改查
员工信息管理是人事管理系统的核心模块。这个模块的两个核心业务逻辑是:员工编号自动生成,以及员工状态变更。
员工编号通常不用自增主键,而要按规则生成,比如部门编码加入职日期加序号,这样在员工档案中能快速定位所属部门和入职批次。实现方式是在 Service 层插入前先查询当前最大编号,然后加一。需要注意并发问题,避免两个员工同时入职时生成重复编号。所以在生成编号的代码上要加同步锁或用数据库唯一索引兜底。
员工新增的 Service 层代码可以这样写:
@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeMapper employeeMapper; @Transactional(rollbackFor = Exception.class) public void addEmployee(EmployeeDTO dto) { // 1. 自动生成员工编号 String maxEmpNo = employeeMapper.selectMaxEmpNo(); String newEmpNo = generateEmpNo(maxEmpNo); // 2. 拷贝属性到实体 Employee employee = new Employee(); BeanUtils.copyProperties(dto, employee); employee.setEmpNo(newEmpNo); employee.setStatus("在职"); employee.setCreateTime(new Date()); // 3. 插入员工主表 employeeMapper.insert(employee); // 4. 如果没有登录账号,自动创建一个默认账号 User user = new User(); user.setEmpId(employee.getId()); user.setUsername(newEmpNo); user.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes())); userMapper.insert(user); } }这段代码值得注意的地方有三个:
@Transactional保证员工表和账号表同时插入成功或同时回滚,避免出现“员工存在但没有登录账号”的脏数据- 默认密码固定为 123456,正式上线时需要改成随机初始密码,并要求首次登录强制修改
- 新增功能写的是
addEmployee,修改、删除的逻辑也类似,删除时使用 update 语句把 status 字段改为“离职”,而不是物理删除
在 Controller 层,参数校验可以直接用 Spring Boot 自带的 @Validated 注解,在 DTO 上给必填字段加 @NotBlank、@Email 等注解,省去手写一堆 if-else。
3.2 员工列表的分页查询与多条件组合筛选
员工列表页是最常见的前端表格场景,需要按部门、姓名、入职日期范围等条件组合筛选。MyBatis-Plus 的分页插件配置好之后,Service 层代码可以这样组织:
public PageResult<EmployeeVO> pageQuery(EmployeeQueryDTO query) { // 1. 构造查询条件 LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(query.getName()), Employee::getName, query.getName()) .eq(query.getDeptId() != null, Employee::getDeptId, query.getDeptId()) .ge(query.getStartDate() != null, Employee::getHireDate, query.getStartDate()) .le(query.getEndDate() != null, Employee::getHireDate, query.getEndDate()) .orderByDesc(Employee::getCreateTime); // 2. 分页查询 Page<Employee> page = new Page<>(query.getPageNum(), query.getPageSize()); Page<Employee> result = employeeMapper.selectPage(page, wrapper); // 3. 组装返回结果 List<EmployeeVO> records = result.getRecords().stream().map(emp -> { EmployeeVO vo = new EmployeeVO(); BeanUtils.copyProperties(emp, vo); // 补充部门名称 Department dept = departmentMapper.selectById(emp.getDeptId()); vo.setDeptName(dept != null ? dept.getDeptName() : "未分配"); return vo; }).collect(Collectors.toList()); return new PageResult<>(records, result.getTotal()); }参数说明:query.getName()为空时,like 条件不生效;deptId为空时,eq 条件不生效。这种做法比拼接 SQL 字符串安全得多,从源头避免了 SQL 注入风险。
3.3 逻辑删除与员工离职流程的设计
员工离职是人事系统里最需要“设计感”的场景。常见的误把离职做成 delete 从表里移除,导致历史薪酬记录关联不上。需要反过来,离职时只更新员工状态字段,同时保留所有业务数据。
推荐的做法是在部门表增加一个关联字段:离职去向,比如 辞职、辞退、退休、合同到期。这些字段在前端对应一个下拉框,在后端是一个普通的 update 操作。真正值得注意的是离职时间与考勤、薪酬的联动:
@Transactional(rollbackFor = Exception.class) public void resignEmployee(Long empId, Date resignDate, String reason) { // 1. 校验员工状态,防止重复离职 Employee emp = employeeMapper.selectById(empId); if (emp == null || "离职".equals(emp.getStatus())) { throw new BusinessException("员工不存在或已离职"); } // 2. 更新员工主表状态 emp.setStatus("离职"); emp.setResignDate(resignDate); emp.setResignReason(reason); employeeMapper.updateById(emp); // 3. 禁用登录账号 User user = userMapper.selectByEmpId(empId); if (user != null) { user.setStatus(0); userMapper.updateById(user); } }员工主表里的 status 字段是核心状态机,在职、试用、离职三个状态互相转换,任何状态变更都走 Service 层统一处理。这样“人事流程”在论文中就有完整的业务闭环。
4. 组织架构树、文件上传和 Excel 导入导出的实战处理
4.1 部门树与员工归属的级联查询
前面讲过部门表的树形结构,实际业务中更常见的一个功能是:按部门树展示员工花名册。这种需求通常是用部门的完整路径来过滤员工列表。比如点击“技术部”,不仅要看到直接归属技术部的员工,还要看到技术部下设的“前端组”“后端组”的所有员工。
处理方案是在部门表上增加 path 字段,例如/0/1/3,其中 0 是虚拟根节点,1 是技术部,3 是后端组。查询某个部门下所有员工时,直接用 path like 拼接条件:
SELECT * FROM employee WHERE dept_id IN ( SELECT id FROM department WHERE path LIKE '/0/1/%' )用 SQL 单表完成,无需递归。这种设计在答辩时讲出来,比对着 JDBC 讲半天更有说服力。
4.2 Spring Boot 上传员工头像与 Excel 批量导入
人事系统中几乎逃不开两个附加功能:上传员工照片和 Excel 批量导入员工数据。Spring Boot 处理文件上传非常简单,Controller 层方法参数直接声明 MultipartFile:
@PostMapping("/import") public Result<String> importEmployees(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("上传文件不能为空"); } // 校验文件格式和大小 String filename = file.getOriginalFilename(); if (!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) { return Result.error("仅支持 Excel 文件"); } if (file.getSize() > 10 * 1024 * 1024) { return Result.error("文件大小不能超过 10MB"); } // 解析 Excel 并批量插入 int successCount = employeeService.importFromExcel(file); return Result.success("成功导入 " + successCount + " 条员工数据"); }上传员工头像时,文件存储路径需要在 application.yml 里配置:
spring: servlet: multipart: max-file-size: 5MB max-request-size: 10MB hrms: upload-path: /data/hrms/avatar/这个配置的含义是两个:单个文件最大 5MB、单次请求最大 10MB。上传路径单独配置,方便部署时通过环境变量覆盖。
Excel 解析推荐用 EasyExcel 或 Apache POI。EasyExcel 的优势是内存占用小,批量导入几千条员工数据不会造成内存溢出。在导入时每一行都要做基本校验,姓名和部门不能为空,手机号格式要正确,校验失败的行要收集错误信息,全部解析完后统一返回,方便前端展示错误明细。
4.3 Spring Boot Actuator 在生产环境中的安全防护
人事系统中如果有 Actuator 依赖,需要注意一个重要问题:Actuator 默认暴露的接口如果没有做权限控制,任何人都能访问/actuator/env、/actuator/heapdump等端点,从而泄露数据库配置、内存堆信息,这就是常见的 Spring Boot Actuator 未授权访问问题。
解决方案是双管齐下。首先用management.endpoints.web.exposure.include=health,info限制只暴露必要的端点;然后给 Actuator 端点加认证,在 Spring Security 配置类中对/actuator/**路径做角色控制,只允许 ADMIN 角色访问。
在开发环境开启、生产环境关闭的配置方式,可以直接放到不同的 profile 文件中:
# application-prod.yml management: endpoints: web: exposure: include: health,info5. 权限控制与 Spring Security 的整合方案
5.1 基于角色的访问控制模型
人事管理系统的权限模型不需要搞得太复杂,基于角色的访问控制(RBAC)足够覆盖常见需求。系统用户分为三个角色:管理员(部门调整、薪资审核、数据导出)、HR(员工信息维护、考勤管理)、普通员工(查看自己的信息)。
Spring Boot 中整合 Spring Security 的流程相对固定:引入依赖、配置 SecurityFilterChain、实现 UserDetailsService、配置 JWT 或 Session。毕设场景下用 Session 加 Spring Security 默认的登录方式就够了,如果要做前后端分离,就换成 JWT。
5.2 激活码登录与权限校验的代码骨架
系统需要支持账号状态控制,比如新员工首次登录要强制改密码、离职员工账号要立即禁用。这个场景可以用 LoginInterceptor 来实现,下面的代码是核心校验逻辑:
@Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { response.setStatus(401); return false; } // 校验 token 是否有效 User user = userService.getUserByToken(token); if (user == null || user.getStatus() != 1) { response.setStatus(401); return false; } // 将用户信息存入 ThreadLocal,供后续业务使用 UserContext.set(user); return true; } }这个拦截器需要注意的是,UserContext用 ThreadLocal 存储当前用户,在请求结束后必须在 afterCompletion 中调用UserContext.clear(),否则线程池复用会导致用户信息串号。Spring Boot 的项目中这个坑非常常见。
注册 WebMvcConfigurer 来注册拦截器并配置放行路径,登录接口、验证码接口要放行,其他接口全部经过拦截器校验。Security 配置中需要同步配置密码加密方式,推荐使用 BCryptPasswordEncoder,基本不用改。
6. 验证与调试:数据初始化脚本和 Actuator 健康检查
6.1 一键初始化数据库的 SQL 脚本
调试阶段最浪费时间的操作是手动建表、造测试数据。我习惯准备一个init-db.sql脚本,包含建表语句和基础的测试数据,每次清空重来只需要执行这一个脚本。
-- department 表 CREATE TABLE IF NOT EXISTS department ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_id BIGINT DEFAULT 0, dept_name VARCHAR(50) NOT NULL, leader_id BIGINT, sort_order INT DEFAULT 0 ); -- employee 表 CREATE TABLE IF NOT EXISTS employee ( id BIGINT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender TINYINT DEFAULT 0, dept_id BIGINT, position VARCHAR(50), hire_date DATE, status VARCHAR(10) DEFAULT '在职', resign_reason VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_dept (dept_id), INDEX idx_status (status) ); -- 初始化部门数据 INSERT INTO department (id, parent_id, dept_name) VALUES (1, 0, '总公司'), (2, 1, '技术部'), (3, 1, '人事部'); -- 初始化员工数据 INSERT INTO employee (emp_no, name, gender, dept_id, position, hire_date, status) VALUES ('EMP001', '张三', 1, 2, 'Java 开发工程师', '2023-03-01', '在职');这段脚本里有两个细节值得注意:一是 emp_no 加了唯一索引,防止并发添加员工时编号重复;二是 department 表没有外键约束,这样可以自由调整部门层级关系,不会被数据库约束卡住。
6.2 健康检查与监控端点的使用
模块调试完成后,需要确认整个应用在生产环境可感知的状态。Spring Boot Actuator 提供了一组监控端点,其中 health 是最常用的。在测试阶段的验证方法很简单:
curl http://localhost:8080/actuator/health上面这个请求会返回{"status":"UP"},说明应用状态健康。引入 Actuator 后的自定义监控项可以在配置文件中进行声明:
management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always其中show-details: always的作用是返回数据库连接状态、磁盘空间等详细信息,定位问题时非常有用。如果不想把这些信息暴露给外部,只需要在前面的权限配置中把/actuator/**路径限制为仅管理员可访问即可。
6.3 论文答辩时的展示技巧
最后说一个实战技巧,论文答辩或项目演示时,与其挨个点菜单展示增删改查,不如先展示这份数据初始化脚本:演示删除全部部门数据、重新执行脚本、再刷新页面,整个系统在一分钟内回到初始状态。这个过程既证明了系统数据模型的完整性,又展示了项目部署的便捷性,远比展示一堆页面截图更有说服力。代码中注意用统一的初始化工具类封装脚本执行逻辑,答辩现场只用一条命令就能完成环境重置,这个操作会直接影响评委对项目完成度的判断。
本文还有配套的精品资源,点击获取