首页 / 资讯中心 / 文章详情

Spring Boot + MyBatis Plus构建企业级HR管理系统全解析

Spring Boot + MyBatis Plus构建企业级HR管理系统全解析 ★ FEATURED ARTICLE
1. 项目定位为什么需要一套自己的HR管理系统做HR系统这件事我在刚接触Spring Boot那阵子就想过了。当时公司用的是一套老旧的Excel邮件流程几百号人的考勤、入离职、薪资核对全靠人力肉搏每个月月底HR都在加班。后来接手过几个外包项目也看过不少开源的人力资源管理系统发现市面上的方案要么太重一堆微服务、中间件小团队根本跑不起来要么太老还在用JSPServlet那套真正适合拿来学习、拿来改、拿来快速交付的Spring Boot单体应用其实不多。这套“springboot人力资源管理系统”就是一个典型的单体架构项目采用Spring Boot MyBatis Plus MySQL的经典组合把组织管理、员工档案、考勤排班、薪资核算、招聘流程这些HR日常最头疼的模块全部收拢到一个系统里。对于正在学Spring Boot的同学来说它的价值在于源码结构清晰、业务场景完整不是那种只有一个CRUD的玩具项目而是能看真实的业务逻辑怎么落地。对于要快速搭一套内部管理系统的团队来说它的价值在于拿来即用改改配置、换换皮肤就能上线不用从零造轮子。我在实际学习和二次开发这套系统的过程中最大的感受是——它的设计思路非常干活导向。所谓干活导向就是它没有堆砌那些看起来很炫的架构而是踏踏实实地把HR业务里的每个环节拆成了可操作的功能模块。比如组织架构支持多级树形展示员工档案覆盖了从入职合同到社保信息的完整字段考勤模块能做班次设定和排班规则薪资模块支持自定义工资项和计算公式。这些功能不是摆设是真正能在企业里跑起来的。接下来我按照自己从拿到源码到跑通、再到底层逻辑拆解、再到二次开发的顺序来写这篇分享。内容会涉及环境准备、模块设计、权限控制、常见翻车点这几个维度希望能帮你少走弯路。2. 环境准备与源码结构解读先跑起来再谈原理2.1 第一步不是写代码而是把依赖版本对齐拿到源码之后我建议你先别急着看业务代码第一步一定是“让项目在你本地跑起来”。这一步翻车的人最多而且绝大部分原因不是代码问题是环境版本不匹配。这套系统的技术栈组合不同的源码包版本会有差异但核心依赖大致如下组件版本参考用途JDK1.8 / 11运行环境Maven3.6依赖管理Spring Boot2.x主框架MyBatis Plus3.xORM与数据库操作MySQL5.7 / 8.0数据存储Redis可选缓存、验证码存储部分版本会用到这里要特别提醒如果你用的是JDK 17以上的环境跑老版本Spring Boot 2.3.x的项目大概率会在启动阶段遇到CGLIB代理、反射访问等方面的问题。我建议老老实实装一个JDK 1.8的版本专门留给这类老项目用别图省事用高版本JDK去跑。反正我试过用JDK 17跑一个Spring Boot 2.2.5的项目启动直接报IllegalArgumentException排查了半天才发现是版本兼容问题白白浪费一个下午。还有MySQL的时区问题JDBC连接串一定要加上serverTimezoneAsia/Shanghai和useUnicodetruecharacterEncodingutf8否则插入中文数据乱码、日期差8小时的问题会接踵而来。2.2 源码结构的“地图”从入口到业务包启动成功后我们来梳理一下源码的整体结构。一个标准的Spring Boot HR系统包结构大概是这样的com.example.hrms ├── config // 配置类MyBatis Plus、拦截器、跨域等 ├── controller // 控制层接收HTTP请求 ├── service // 业务层核心业务逻辑 │ └── impl // 业务实现类 ├── mapper // 数据访问层MyBatis Plus的Mapper接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接收前端参数 ├── vo // 视图对象返回给前端的数据 ├── common // 通用工具结果封装、异常处理、常量 ├── utils // 工具类日期、Excel导入导出等 └── HrmsApplication.java // 启动类这个分层是典型的单体分层架构理解它的关键不在于记住每个包的名字而是搞清楚数据是怎么流的。我给你捋一下一条“新增员工”的完整链路前端把表单数据POST到/employee/add。控制层EmployeeController收到请求用RequestBody接收DTO简单参数校验之后调用EmployeeService。业务层EmployeeServiceImpl拿到DTO先做业务校验比如工号是否重复然后把DTO转换成Employee实体对象调用EmployeeMapper的insert方法。EmployeeMapper继承BaseMapperEmployee所以基础的增删改查方法不需要自己写SQLMyBatis Plus已经帮你封装好了。插入成功后返回统一结果对象Result.success()前端根据里面的code判断成功与否。如果你之前只写过单体CRUD的项目这套流程可能已经比较熟悉了。但HR系统和一个普通的管理系统相比真正难的不是增删改查而是几个核心业务模块的数据结构和状态流转。下面我就按模块来拆。3. 核心业务模块拆解考勤、排班、薪资这几个硬骨头3.1 组织架构管理树形结构的两种实现方式组织架构是HR系统的地基。一个公司有总公司、分公司、部门、小组层级关系天然就是一棵树。这个模块在数据表设计上比较常见的做法是dept_id加上parent_id字段也就是每一条部门记录都记录它的上级部门ID根节点的parent_id设为0。查询树形结构有两种常用方式我在这个系统里都试过第一种是递归查询从根节点开始每查一层就调一次数据库。优点是逻辑直白缺点是数据层级多了之后SQL查询次数暴涨对性能不太友好。第二种是一次查出所有部门在内存中组装树。这种方式只需要一次查询然后在Java代码里用Map把所有的部门节点按parent_id分组再循环拼接成层级结构。在我实际测试中几千条部门数据组装成树也就几十毫秒完全够用。组装树的代码逻辑不复杂核心思路是先把所有节点放进Map然后遍历每个节点找到父节点把自己挂到父节点的children列表里public ListDeptVO buildTree(ListDept deptList) { MapLong, DeptVO voMap new HashMap(); for (Dept dept : deptList) { DeptVO vo new DeptVO(); BeanUtils.copyProperties(dept, vo); vo.setChildren(new ArrayList()); voMap.put(dept.getId(), vo); } ListDeptVO tree new ArrayList(); for (DeptVO vo : voMap.values()) { if (vo.getParentId() 0) { tree.add(vo); } else { DeptVO parent voMap.get(vo.getParentId()); if (parent ! null) { parent.getChildren().add(vo); } } } return tree; }这套系统用的是第二种方式也就是一次查询内存组装。实际效果很好接口响应时间稳定在100毫秒以内数据量在千级规模时。3.2 员工档案模块字段设计远比你想的复杂员工档案看起来就是一个“增删改查”实际做起来字段特别多。拿这套系统来举例光员工表就涵盖了基本信息姓名、性别、出生日期、身份证号、联系方式手机、邮箱、紧急联系人、任职信息工号、部门、岗位、入职日期、合同信息合同类型、开始日期、结束日期、学历信息学历、毕业院校、专业、薪资信息基本工资、岗位工资、绩效基数等几十个字段。这种宽表设计在中小型系统里是合理的因为查询的时候不需要关联多张表性能好。但有一条经验供参考像身份证号、薪资这些敏感字段响应给前端的时候要么脱敏要么按需返回别把所有字段一把梭直接丢出去。很多系统源码里没有做脱敏你自己做二次开发的时候要注意这个点。员工模块里还有一个比较有用的功能是员工状态流转也就是从“待入职”到“在职”再到“离职”的全生命周期。这套系统的实现方式是Employee表里维护一个status字段用数字表示状态比如0待入职1在职2离职。业务操作上入职审批通过后把状态置为1离职审批通过后把状态置为2同时记录对应的操作时间和操作人。3.3 考勤与排班这是整个系统里最容易做乱的部分考勤模块做得好不好直接决定了HR月底统计的工作量。这个模块的核心实体是三个班次表、排班表、打卡记录表。先说班次。一个班次包含上下班时间、允许的迟到分钟数、是否跨天比如夜班22:00到次日6:00等属性。这套系统在跨天设置上处理得比较好它会在排班时判断班次的结束时间是否小于开始时间如果是说明跨天计算工作时长时自动加上24小时。再说排班。排班有“固定班次”和“轮班制”两种模式系统里比较常见的是直接给员工绑定一个班次ID这样每天按照同一个班次来考勤。复杂一点的做法是支持排班日历每天可以给不同员工设置不同班次虽然灵活但排班界面和底层数据结构的复杂度都会上一个台阶。打卡记录和班次的匹配是考勤模块里最绕的地方。你可以这么理解员工实际打卡时间是一串时间戳系统要做的判断是——这个时间戳落在哪个班次的哪个区间内。比如上午9点到12点之间打的卡算上班卡下午6点到次日凌晨2点之间打的卡算下班卡。这里有一个隐藏的坑跨天班次的日期归属问题。比如夜班员工在凌晨1点打卡下班这张打卡记录应该归属到昨天的排班上而不是今天。系统里处理这种问题的方法通常是把yyyy-MM-dd HH:mm:ss格式的打卡时间先做一次归一化处理判断是否属于次日凌晨如果是则归属到前一天。3.4 薪资核算模块公式自定义是刚需薪资模块是HR最敏感、也是最不能出错的模块。这套系统的薪资计算逻辑采用了“基础工资项 自定义工资项”的模式。基础工资项包括基本工资、岗位工资、绩效工资、加班费、社保扣款、个税等这些是固定的。自定义工资项允许管理员在后台配置工资项名称、计算方式固定金额/按公式和计算公式。举个例子餐饮补贴计算公式可以写成基本工资 * 0.1 200系统在核算薪资时会解析这个公式把对应工资项的值代入计算。公式解析的实现方式一般有两种选择用脚本引擎比如Groovy或者SpEL优点是灵活强大缺点是有安全风险必须做好沙箱隔离。自己写一个简单的四则运算解析器支持加减乘除和括号优点是轻量、无副作用缺点是复杂逻辑写不了。一套靠谱的HR系统薪资计算的流程大致是先根据考勤数据算出应出勤天数和实际出勤天数然后计算加班费再走一遍设定的工资项公式得到应发工资减去社保和个税得到实发工资。最后生成薪资条推送给员工确认。我特别想提醒一点薪资计算不要用double或float一定要用BigDecimal。这不是技术洁癖是实打实的踩坑经验。用浮点数计算工资0.10.2可能等于0.30000000000000004这在薪资场景里是要出大事的。涉及钱的字段从实体类到数据库decimal类型全部统一用BigDecimal。4. 权限与安全设计菜单权限和数据权限分开管控4.1 RBAC模型用户、角色、菜单的三层关系HR系统里不同角色能看到的数据差异很大普通员工只能看自己的考勤和薪资部门主管能看本部门数据HR管理员能看全公司数据超级管理员能做系统配置。这就涉及到权限设计了。这套系统的权限模型是经典的RBAC基于角色的访问控制模型核心是三张表加上两张关联表用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户不直接拥有权限而是通过角色间接获取权限。后端登录成功后会返回给前端当前用户的角色编码和菜单列表前端根据这些数据动态生成侧边栏菜单同时对路由做过滤。具体到Spring Boot代码层面实现手段通常有两种一种是编写拦截器/过滤器在请求进入Controller之前校验当前用户是否有某个URL的访问权限。优点是控制粒度细缺点是每个请求都要查一次数据库性能有损耗一般会配合Redis缓存权限数据。另一种是基于注解的权限控制在Controller的方法上加上类似RequiresPermissions(employee:add)的注解由AOP统一拦截校验。优点是可读性好、开发效率高缺点是权限标识需要在代码里维护调整起来要重新发版。我实际测试下来两种方式组合使用是最合理的粗粒度的菜单权限用数据库配置控制细粒度的操作权限比如“能否导出员工数据”“能否修改薪资”用注解控制。系统源码里如果只实现了一种你在二次开发的时候可以按这个思路补上另一种。4.2 数据权限不能只看菜单还要看数据范围菜单权限解决的是“能不能看到这个页面”数据权限解决的是“能看到哪些数据”。这两个概念很容易混在一起。比如“考勤管理”这个菜单普通员工进不去部门主管能进去但只能看到自己部门的数据HR能看到全部数据——这就不是菜单权限能解决的需要额外的数据权限控制。实现数据权限的通用套路是在SQL层面做文章。常用的做法是在查询语句里根据当前用户的角色动态拼接过滤条件。比如部门主管查询员工列表时自动加上WHERE dept_id ?这里的值取当前用户所属部门HR管理员查询时不加这个条件看到全公司数据。在MyBatis Plus中可以通过TenantLineInnerInterceptor或者自定义Interceptor来实现这种数据权限的自动注入。说实话这块如果做得不好系统上线后很容易出现“信息越权”问题尤其HR系统涉及薪资和个人隐私风险会被放大。我自己做二次开发时会格外注意凡是涉及员工薪资、身份证号、家庭住址等敏感数据的查询接口都过一遍数据权限校验不能只靠前端隐藏字段来防君子。5. 从源码到能上线二次开发常踩的坑与优化建议5.1 坑1角色初始化数据没有配套SQL我拿到一套HR系统源码后第一步就是看它的初始化SQL脚本一般是sql/init.sql或doc/database.sql。这个脚本里除了建表语句还应该包含初始的管理员账号、角色数据、菜单数据。如果脚本里只有建表没有数据启动后你会发现连登录都做不了。遇到这种情况处理办法是翻控制器里的登录逻辑找到它校验的账号密码存储位置然后手写一条INSERT语句把账号密码密码需要是BCrypt加密后的密文插到用户表里再手动关联一个超级管理员角色。5.2 坑2跨域配置和前端联调如果你用的是前后端分离模式前端Vue后端Spring Boot跨域问题就是第一道坎。源码里通常会在Config包下有个CorsConfig或者直接在启动类里加CrossOrigin。建议使用全局配置的方式Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意这里有个细节如果allowCredentials(true)那么allowedOriginPatterns不能写成*必须指定具体的域名否则浏览器会直接拦截响应。很多同学在这个地方卡一下午其实就是这个原因。5.3 优化1列表查询接口一定要做分页和条件过滤看源码的时候很多人会忽略一个点列表接口有没有做分页如果所有查询都是SELECT * FROM employee然后把全量数据返回给前端那最多几千条数据就要开始卡了。MyBatis Plus提供了PageE分页参数配合selectPage方法可以一行代码搞定分页。如果你在二次开发时发现某个列表接口没有分页优先补上PageEmployeeVO page new Page(pageNum, pageSize); LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(keyword), Employee::getName, keyword); wrapper.eq(deptId ! null, Employee::getDeptId, deptId); PageEmployee result employeeMapper.selectPage(page, wrapper);5.4 优化2密码加密不要用MD5有些老的源码会用MD5加密存储密码这在今天的安全环境下是不够安全的。MD5已经被证明可以快速碰撞破解作为HR系统里管理员工敏感数据的应用密码加密至少要升级到BCrypt。升级的方式很简单Spring Security自带BCryptPasswordEncoder你可以在登录方法里把原先的MD5比对逻辑替换掉。如果你的系统没有引入Spring Security也可以用hutool工具库的BCrypt工具类直接在工具类里定义加密校验方法。5.5 优化3Excel导入导出不能少HR系统的员工档案和考勤数据几乎百分之百要和Excel打交道。源码里通常会有基于Apache POI或EasyExcel实现的导入导出工具类。这里我的建议是如果源码用的是原生POI二次开发时可以考虑替换为EasyExcelExcel导入导出这块EasyExcel在内存占用和API易用性上比原生POI友好太多尤其导出几万条数据的时候原生POI的OOM问题真的很让人崩溃。EasyExcel导出的核心写法也不复杂用注解就能完成字段映射Data public class EmployeeExcelVO { ExcelProperty(工号) private String empNo; ExcelProperty(姓名) private String name; ExcelProperty(部门) private String deptName; }然后一行导出EasyExcel.write(response.getOutputStream(), EmployeeExcelVO.class) .sheet(员工信息) .doWrite(dataList);6. 一套可复用的二次开发路线图最后分享一套我自己在拿到任何一套Spring Boot管理系统源码后的固定处理流程也适用于这套HR系统。你先照这个顺序走一遍基本不会乱先看数据库脚本把建表和初始化数据导入本地MySQL用数据库客户端把表结构通读一遍理清表和表之间的关系。这是理解整个系统的钥匙。跑通登录用初始化账号登录从前端页面反推后端接口。前端调用了哪些接口、每个接口返回什么结构用浏览器开发者工具的Network面板就能看得清清楚楚。梳理核心链路选定一个核心业务比如员工入职从前端提交→Controller→Service→Mapper一步一步跟完整个链路。跟过一条链路之后其他模块基本就是复制这个思路。定位可复用组件看看系统里有哪些通用组件可以抽出来复用比如文件上传、操作日志、Excel工具、统一异常处理这些是以后做其他项目能直接搬走的资产。按需增强针对自己业务场景补功能。最常需要补的是批量操作批量导入员工、批量排班、导出报表、操作日志审计。安全加固改默认密码、升级密码加密算法、敏感字段脱敏、接口限流。HR系统的数据太敏感了上线前这一步不能省。我在给团队内部分享时经常说学Spring Boot最忌惮的就是只跟着教程写Demo。那种项目教不会你真正的业务设计能力。而一套完整的HR管理系统恰好能覆盖一个后端开发者在真实工作中会遇到的大部分问题——树形结构、复杂查询、状态流转、权限控制、金额精度、Excel导入导出每一个拿出来都是实打实的面试考点和工程经验。如果你也在学Spring Boot或者正准备接手一套类似管理系统项目不如就按上面说的顺序把源码拆一遍、跑一遍、改一遍。等你把员工从入职到离职的整条生命周期在这个系统里完整走通你对Spring Boot单体应用的理解会比刷十遍文档都扎实。
阅读完成 · 觉得有帮助?
咨询建站