新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java 人事管理系统:Spring Boot 权限与考勤薪资实现

发布时间:2026/9/18 23:00:56来源:尧图网络
Java 人事管理系统:Spring Boot 权限与考勤薪资实现
简介这份文档资料为基于Java的人事管理系统毕业设计论文面向计算机相关专业的本科生、课程设计者及需要项目实战参考的开发者帮助解决从需求分析到系统落地的完整设计思路问题。压缩包共1个文件为一份doc格式论文文档体积约982KB内容篇幅完整可直接用于选题参考、框架借鉴与格式对照。论文以Java为开发语言、MyEclipse为开发环境、Access为数据库平台依次展开信息化发展与研究背景、关键技术介绍、经济与技术及操作可行性分析、系统功能结构与工作流设计、数据库表设计与E-R图并细化到登录界面、基础信息管理中的添加修改删除查询、部门管理、人员调动与调动历史查询、人员考核、劳资分配与劳资历史查询等模块最后包含程序调试、测试方法及总结致谢。目前已有681人学习下载适合作为同类管理系统开发的需求梳理、模块划分与文档撰写范本。1. 一个能跑起来的人事管理系统卡点从来不在增删改查很多刚学完 Java 基础的同学做人事管理系统时第一反应是打开 IDE 建个 Spring Boot 工程写几张表、配几个 Controller然后发现真正卡住的地方根本不是 CRUD员工工号要不要唯一、部门调整后历史考勤归属怎么算、离职员工的账号什么时候失效、薪资变动怎么留痕。这些问题不解决代码写得再漂亮业务上一问就露怯。这篇要讲的是围绕基于 Java 的人事管理系统这套东西把技术选型、表结构、后端接口、权限控制和部署验证串成一条能复现的线。适合两类人一是正在做课程设计或毕业设计、需要一套讲得清楚又能跑通的方案的人二是已经会写 Java、但第一次正经设计一套带权限和审计的业务系统的后端同学。默认你会 Java 基础、能配好环境变量、跑得动 Maven 和 MySQL剩下的我们一步步来。2. 人事管理系统的技术选型与数据模型设计动手之前先定两件事用什么技术栈以及数据怎么建模。选型决定后续能不能扩展建模决定后面会不会推倒重来。很多系统做不下去是因为表设计时把人和岗位部门混在一张表里改一次组织架构就要动几十处代码。2.1 为什么选 Spring Boot MyBatis-Plus MySQL 这套组合Java 后端做管理系统主流方案基本就是 Spring Boot 打底持久层在 MyBatis-Plus 和 Spring Data JPA 之间二选一。我一般推荐 MyBatis-Plus原因很实际人事系统里大量是多条件组合查询 分页 明细字段拼接比如查某部门某时间段入职且状态为在职的员工这种 SQL 用 JPA 写起来要么拼 Specification要么掉进 N1 查询用 MyBatis 直接写 XML 或注解 SQL字段可控、执行计划可调。下面是pom.xml里必须对齐的几个依赖版本Spring Boot 3.x 对应的是 Jakarta 命名空间别拿 2.x 的代码直接套properties java.version17/java.version mybatis-plus.version3.5.5/mybatis-plus.version /properties dependencies !-- Web 层提供 REST 接口能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 启动器自带分页、逻辑删除等通用能力 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency !-- MySQL 驱动8.x 用 com.mysql.cj.jdbc.Driver -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependenciesjava.version设成 17 是因为 Spring Boot 3.x 的最低要求是 17如果你环境里只有 8就退回 Spring Boot 2.7 并把依赖坐标换成mysql-connector-java。MyBatis-Plus 的 3.5.x 兼容性最好低于 3.4 的版本在 Spring Boot 3 下会有javax与jakarta冲突。这里提一句java环境变量配置没弄对的话Maven 编译会直接报JAVA_HOME not found先确认java -version和mvn -v都能正常输出版本号再往下走。2.2 员工、部门、岗位、账号四张核心表的字段怎么定人事系统的数据模型核心是把组织结构和人员解耦。我的做法是四张主表sys_dept部门、sys_position岗位、sys_employee员工、sys_user登录账号。员工属于部门、挂在岗位上账号通过employee_id关联员工这样一个自然人可以有账号也可以没有比如实习期账号可以停用而复职后再启用。关键字段设计如下表名关键字段类型说明sys_deptdept_id / parent_id / dept_pathbigint / bigint / varchar(500)dept_path 存 1/3/7 形式的祖级路径便于查子树sys_positionposition_id / position_code / gradebigint / varchar(32) / intgrade 决定薪资档位工号规则可挂这里sys_employeeemp_no / dept_id / position_id / hire_date / statusvarchar(32) / bigint / bigint / date / tinyintemp_no 唯一索引status 用 1 在职 2 离职 3 试用sys_useruser_id / employee_id / password / status / del_flagbigint / bigint / varchar(100) / tinyint / tinyintdel_flag 做逻辑删除凡是审计场景禁物理删dept_path这个字段值得多说一句很多教程里没有但组织架构查询离不开它。存成0/1/5/这种形式后要查研发中心及其所有子部门下的员工一句WHERE dept_path LIKE 0/1/5/%就够了不用递归。代价是部门移动时要级联更新子树的 path这个逻辑写在 Service 里用一条UPDATE sys_dept SET dept_path REPLACE(dept_path, #{oldPrefix}, #{newPrefix}) WHERE dept_path LIKE #{oldPrefix} || %完成。表格里所有bigint主键都用雪花 ID 或自增课程设计里自增就够生产环境建议雪花避免分库冲突。2.3 建表 SQL 与初始化数据的一次性脚本理论说完直接给能执行的脚本MySQL 8.0 下可以直接跑。注意字符集统一utf8mb4否则员工姓名里的生僻字会变成问号这个坑在真实录入时很常见。CREATE DATABASE IF NOT EXISTS hr_system DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE hr_system; CREATE TABLE sys_dept ( dept_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 部门ID, parent_id BIGINT NOT NULL DEFAULT 0 COMMENT 父部门ID0为顶级, dept_name VARCHAR(64) NOT NULL COMMENT 部门名称, dept_path VARCHAR(500) NOT NULL DEFAULT COMMENT 祖级路径形如 0/1/5/, order_num INT NOT NULL DEFAULT 0 COMMENT 排序, PRIMARY KEY (dept_id), KEY idx_parent (parent_id) ) ENGINEInnoDB COMMENT部门表; CREATE TABLE sys_employee ( emp_id BIGINT NOT NULL AUTO_INCREMENT, emp_no VARCHAR(32) NOT NULL COMMENT 工号业务唯一, emp_name VARCHAR(64) NOT NULL, dept_id BIGINT NOT NULL, position_id BIGINT NOT NULL, hire_date DATE NOT NULL COMMENT 入职日期, leave_date DATE NULL COMMENT 离职日期在职为 NULL, status TINYINT NOT NULL DEFAULT 3 COMMENT 1在职 2离职 3试用, del_flag TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1已删, PRIMARY KEY (emp_id), UNIQUE KEY uk_emp_no (emp_no), KEY idx_dept_status (dept_id, status) ) ENGINEInnoDB COMMENT员工表; -- 初始化顶级部门parent_id0 是约定 INSERT INTO sys_dept (parent_id, dept_name, dept_path, order_num) VALUES (0, 总公司, 0/, 1);emp_no上的唯一索引是必须的把它交给数据库约束比在 Java 里select一次再判断要可靠高并发下后者必然出重复。idx_dept_status这个联合索引对应的是最高频的查询按部门筛在职人员索引顺序不能反status放后面是因为它的区分度低。status默认值给 3试用而不是 1是防止插入时漏传导致新员工直接显示为正式在职。3. 员工与部门模块的后端接口实现表建好之后真正要写的是接口。这一章按部门树怎么查、员工怎么查怎么改、批量导入怎么做三层展开代码都基于 MyBatis-Plus 的分页和条件构造器。很多人写管理系统时把 Controller 写得极厚业务逻辑全塞在接口方法里后面加个字段就要改三处所以下面的组织方式是 Controller 只做参数校验和调用逻辑放 Service。3.1 部门树查询一次查全表在内存里拼更划算部门数据量通常在几百到几千条属于典型的读多写少、量小这种场景不要用递归 SQL 逐层查。我一般一次selectList把全表捞出来在内存里按parent_id分组递归拼树避免 N1。写法如下Service public class DeptServiceImpl implements DeptService { Resource private DeptMapper deptMapper; Override public ListDeptTreeVO listTree() { // 一次性捞出全部未删除部门 ListDept all deptMapper.selectList( new LambdaQueryWrapperDept().orderByAsc(Dept::getOrderNum)); // 按 parentId 分组key 为父IDvalue 为该父下的子部门 MapLong, ListDept group all.stream() .collect(Collectors.groupingBy(Dept::getParentId)); // 从根节点(父ID0)开始递归构建 return build(0L, group); } private ListDeptTreeVO build(Long pid, MapLong, ListDept group) { ListDept children group.getOrDefault(pid, Collections.emptyList()); return children.stream().map(d - { DeptTreeVO vo new DeptTreeVO(); vo.setId(d.getDeptId()); vo.setLabel(d.getDeptName()); vo.setChildren(build(d.getDeptId(), group)); // 递归子节点 return vo; }).collect(Collectors.toList()); } }groupingBy(Dept::getParentId)负责把线性列表变成父子映射build从pid0开始逐层取子节点天然避开对数据库的重复查询。这个方法配合Cacheable缓存后部门树基本不会成为性能瓶颈。参数上注意getOrDefault返回空列表而不是 null否则前端拿到的children是 null 会渲染报错。3.2 分页查询员工条件构造器 手工 SQL 的分工员工列表查询是频率最高的接口参数一般有部门、状态、关键字工号或姓名、入职时间区间、页码和每页条数。基础过滤用LambdaQueryWrapper拼涉及部门子树这种需要LIKE路径的场景就写自定义 SQL。先看 Controller 和 Service 的分工GetMapping(/page) public ResultPageEmployeeVO page(EmployeeQuery query) { return Result.ok(employeeService.pageQuery(query)); } // Service 实现 public PageEmployeeVO pageQuery(EmployeeQuery q) { PageEmployeeVO page new Page(q.getPageNum(), q.getPageSize()); LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.eq(Employee::getDelFlag, 0) .eq(q.getStatus() ! null, Employee::getStatus, q.getStatus()) .ge(q.getHireDateStart() ! null, Employee::getHireDate, q.getHireDateStart()) .le(q.getHireDateEnd() ! null, Employee::getHireDate, q.getHireDateEnd()) .and(StringUtils.hasText(q.getKeyword()), w - w .like(Employee::getEmpNo, q.getKeyword()) .or().like(Employee::getEmpName, q.getKeyword())) .orderByDesc(Employee::getHireDate); // 部门子树过滤走自定义 SQL传 deptPath 前缀 if (q.getDeptId() ! null) { String path deptMapper.selectPathById(q.getDeptId()); return employeeMapper.pageByDeptPath(page, path, wrapper); } return employeeMapper.selectPage(page, wrapper); }这里的重点是条件构造器里那个eq(判断, 字段, 值)的三参重载第一个参数为 false 时整个条件不拼接省掉一堆if。关键字查询用and(w - ...)把工号和姓名包成一组否则和前面的状态条件会形成OR泄漏查出不该有的数据这是条件构造器最容易踩的坑。部门过滤传deptPath前缀给自定义 SQL对应 Mapper 里的select idpageByDeptPath resultTypecom.hr.vo.EmployeeVO SELECT e.emp_id, e.emp_no, e.emp_name, d.dept_name, p.position_name, e.status, e.hire_date FROM sys_employee e LEFT JOIN sys_dept d ON e.dept_id d.dept_id LEFT JOIN sys_position p ON e.position_id p.position_id WHERE e.del_flag 0 AND d.dept_path LIKE CONCAT(#{path}, %) ${ew.customSqlSegment} /select${ew.customSqlSegment}是 MyBatis-Plus 注入 wrapper 条件的固定写法path是部门路径前缀CONCAT拼出LIKE 0/1/5/%的效果。注意这里用dept_path做过滤而不是dept_id因为要含子部门。3.3 新增与修改员工工号唯一性和离职状态的校验新增接口要处理的边界比查询多工号重复、部门或岗位不存在、离职状态不能有离职日期为空等。把这些校验收进 Service用事务包住插入员工 更新账号关联这一组操作Transactional(rollbackFor Exception.class) public void addEmployee(EmployeeAddDTO dto) { // 1. 工号唯一性校验靠唯一索引兜底这里先查是为了给友好提示 Long cnt employeeMapper.selectCount( new LambdaQueryWrapperEmployee().eq(Employee::getEmpNo, dto.getEmpNo())); if (cnt 0) { throw new BizException(工号已存在 dto.getEmpNo()); } // 2. 校验部门与岗位是否有效 if (deptMapper.selectById(dto.getDeptId()) null) { throw new BizException(部门不存在); } // 3. 组装并落库试用期默认状态3 Employee emp new Employee(); BeanUtils.copyProperties(dto, emp); emp.setStatus(3); emp.setDelFlag(0); if (EmployeeStatusEnum.LEFT.getCode().equals(dto.getStatus())) { // 标记离职时离职日期不能空 Assert.notNull(dto.getLeaveDate(), 离职日期不能为空); } employeeMapper.insert(emp); }Transactional的rollbackFor Exception.class不能省默认只回滚运行时异常业务异常继承自Exception时不会回滚。工号校验先查再插只是体验优化真正防重的是 2.3 节那张表上的唯一索引并把DuplicateKeyException在全局异常处理器里转成友好提示。BeanUtils.copyProperties用来复制同名字段绝大部分教程都用它但注意它不处理类型不一致的字段hire_date从前端传来的字符串要先解析成Date再拷。4. 权限控制与考勤薪资的联动处理管理系统到这一步能用了但人事系统绕不开两个真实需求谁能看谁的薪资以及考勤结果怎么进薪资。前者靠 RBAC 权限模型后者靠定时任务和状态流转。这两块写好了系统的业务感才立得住。4.1 用 RBAC 模型控制字段级权限普通 RBAC 只能控制能不能访问某个接口但薪资明细要控制到字段HR 能看全部门主管只能看本部门员工的薪资档位普通员工只能看自己。做法是在角色和权限之间加一层数据范围data_scope并在返回值上做字段脱敏。角色表加data_scope字段取值 1 全部、2 本部门及子部门、3 本部门、4 仅本人权限校验时按当前用户的角色取最小范围拼进查询条件// 在查询薪资前追加数据范围条件 public LambdaQueryWrapperSalary applyScope(LambdaQueryWrapperSalary w, LoginUser user) { Integer scope user.getDataScope(); switch (scope) { case 1: return w; // 全部数据不加限制 case 2: // 本部门及子部门复用 dept_path 前缀 return w.likeRight(Salary::getDeptPath, user.getDeptPath()); case 3: return w.eq(Salary::getDeptId, user.getDeptId()); default: return w.eq(Salary::getEmpId, user.getEmpId()); } }likeRight就是LIKE xxx%的封装正好对上dept_path前缀。字段级脱敏在 VO 序列化时做用一个Sensitive注解加 Jackson 的JsonSerializer判断当前用户有没有salary:detail权限没有就把实际金额替换成***。角色data_scope可见薪资字段可见范围系统管理员1全部全公司HR 专员1全部全公司部门主管2档位总额本部门及子部门普通员工4仅本人明细本人4.2 考勤数据入库与月度薪资的定时结算考勤和薪资的联动核心是定时聚合。每月固定时间跑一次任务把当月考勤汇总成薪资计算所需的基础数据。用 Spring 的Scheduled就够别一上来就上分布式调度Component public class SalarySettleTask { Resource private AttendanceMapper attendanceMapper; Resource private SalaryService salaryService; // 每月 1 号凌晨 2 点结算上月薪资cron 六位秒 分 时 日 月 周 Scheduled(cron 0 0 2 1 * ?) public void settle() { // 上月区间 LocalDate last LocalDate.now().minusMonths(1); LocalDate start last.withDayOfMonth(1); LocalDate end last.withDayOfMonth(last.lengthOfMonth()); // 按员工聚合考勤出勤天数、请假天数、加班时长 ListAttendanceSum sums attendanceMapper.sumByMonth(start, end); // 逐人计算薪资内部按 grade 取档位、扣减缺勤 sums.forEach(s - salaryService.calculate(s, start, end)); } }cron表达式六位含义是秒 分 时 日 月 周?表示不指定这里表示每月 1 号 2 点 0 分 0 秒。sumByMonth用GROUP BY emp_id在数据库聚合别把明细捞到 Java 里循环。结算方法要加幂等判断salary表上建(emp_id, salary_month)的唯一索引重复跑时用INSERT ... ON DUPLICATE KEY UPDATE覆盖而不是新增否则定时任务重试会出双份工资。4.3 接口调试与幂等验证的常用手段写完接口一定要验证尤其是并发和重复提交场景。最直接的办法是用curl连打两次新增接口看第二次是否因为唯一索引报错或走了幂等逻辑# 两次提交同一工号观察返回差异 curl -X POST http://localhost:8080/api/employee \ -H Content-Type: application/json \ -H Authorization: Bearer token \ -d {empNo:E2024001,empName:张三,deptId:1,positionId:1,hireDate:2024-06-01}第一次应返回成功第二次应返回工号已存在。如果第二次也成功说明唯一索引没建上或者逻辑删除干扰了唯一约束——del_flag参与唯一索引时删除后再新增同工号会被数据库拦下这时得把唯一索引改成(emp_no, del_flag)并用时间戳填充del_flag来保证软删后能复用。这个差异用两条命令就能验出来比在页面上点半天可靠。5. 部署上线前必须跑的三类验证系统能跑通不代表能交付上线前我最少做三类验证。第一类是数据一致性随机挑几个员工比对页面显示的部门和数据库dept_path是否吻合重点看有没有挂在子部门但路径前缀是父部门的脏数据。第二类是权限越权用普通员工账号直接请求薪资明细接口看是否被数据范围条件拦住用 Postman 把 token 换成低权限用户重放即可比对返回条数。第三类是并发与边界。给员工新增接口做一次并发压测用ab或 JMeter 打 50 并发同工号验证唯一索引确实生效、没有产生重复数据再把入职日期传成未来日期、离职日期早于入职日期看校验是否给出明确提示而不是 500。这里给一个查脏数据的 SQL交付前跑一遍-- 找出 dept_id 指向的部门其 dept_path 与员工归属路径不一致的脏数据 SELECT e.emp_id, e.emp_no, d.dept_name, d.dept_path FROM sys_employee e JOIN sys_dept d ON e.dept_id d.dept_id WHERE e.del_flag 0 AND d.dept_path NOT LIKE CONCAT( (SELECT p.dept_path FROM sys_dept p WHERE p.dept_id e.dept_id), %);这个查询的逻辑是员工当前部门自身的 path 理应等于查询基准若NOT LIKE命中说明部门 path 在级联更新时漏改通常是部门移动后没刷子树。发现后按 2.2 节的REPLACE语句批量修正再重跑一次确认结果为空。最后留一个实际技巧交付前把spring.jackson.time-zone显式设成GMT8并把日期字段统一用JsonFormat(pattern yyyy-MM-dd)标注。人事系统里入职、离职、考勤日期跨时区错一天业务上就是无效数据而这个坑在本地开发时几乎不会暴露往往等部署到服务器上、时区变了才炸出来。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

BP神经网络时序预测:滑窗长度与多窗口平均策略 2026/9/19 0:01:07

BP神经网络时序预测:滑窗长度与多窗口平均策略

简介:面向机器学习、深度学习与数据建模学习者的一份完整研究文献,聚焦BP神经网络在农业产量预测中的应用。文档以1980—2018年全国棉花产量为样本,系统讲解数据归一化处理、激活函数原理、多层神经网络结构搭建及训练流程,展示敏…

阅读更多 →
企业GEO服务商的核心价值与技术应用解析 2026/9/19 0:01:07

企业GEO服务商的核心价值与技术应用解析

1. 企业GEO服务商的核心价值解析在数字化转型浪潮中,地理空间数据服务(GEO服务)正成为企业营销战略的关键基础设施。不同于传统的地理位置服务,现代GEO服务商提供的是一套融合空间智能、人群画像和实时数据的综合解决方案。以某零…

阅读更多 →
基于陷波滤波器的双惯量系统谐振抑制仿真与实践 2026/9/19 0:01:07

基于陷波滤波器的双惯量系统谐振抑制仿真与实践

1. 项目概述作为一名在伺服控制领域摸爬滚打多年的工程师,我深知机械谐振问题就像系统里的"隐形杀手"。最近用Matlab/Simulink搭建了一套基于陷波滤波器的双惯量系统谐振抑制仿真模型,今天就把这个"振动克星"的实现过程掰开揉碎讲清…

阅读更多 →
统信UOS上LocalSend的进阶玩法:文本发送与剪贴板联动指南 2026/9/19 0:01:07

统信UOS上LocalSend的进阶玩法:文本发送与剪贴板联动指南

如果你用的是统信UOS,应该多少经历过这种尴尬:手机里刚拍的照片想放到电脑上,微信传过去画质被压得惨不忍睹;办公电脑上做好的表格想发到手机,U盘来回拔插还总担心文件损坏;第三方网盘装了一堆,…

阅读更多 →
2026惠州电气检测机构排名 TOP5 CMA 资质机构提供防爆设备检测+防爆安全检测 联系方式推荐 2026/9/19 0:01:07

2026惠州电气检测机构排名 TOP5 CMA 资质机构提供防爆设备检测+防爆安全检测 联系方式推荐

惠州本地电气防爆检测机构鳞次栉比,但资质水平参差不齐、鱼龙混杂。化工园区、油库加油站、矿山厂区、制药企业及危化品仓储场所进行防爆电气安全排查与生产验收时,大量无资质机构出具的检测报告往往无法通过应急管理部门核查,导致企业反复整…

阅读更多 →
警务数据协同架构:语义注册、热加载规则与低带宽协议 2026/9/18 23:58:07

警务数据协同架构:语义注册、热加载规则与低带宽协议

简介:本资源是一份面向公安信息化建设从业者的智慧警务整体解决方案PPT,聚焦大数据、云计算与物联网技术在公安实战中的深度集成应用,系统回应城市治安升级、指挥层级压缩、情报支撑不足等核心业务挑战。文件为单个5.25MB的PPT文档&#xff0…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞