本文还有配套的精品资源点击获取简介这个人事管理后台系统用SpringBoot 2.x做后端MyBatis-Plus操作MySQL前端基于jQuery和Layui实现响应式界面。功能覆盖招聘管理、员工档案、考勤记录、薪资核算、绩效评估、培训安排、系统设置七大业务板块每个模块都支持实际业务流程操作。权限体系分四级管理员、财务专员、人事专员、普通用户预设账号密码如admin/admin、cw/cw等登录即用。权限控制细化到菜单和按钮级别所有权限规则存于数据库修改后实时生效不用重启服务。项目结构规范包含完整Java源码src目录、Maven配置pom.xml、详细README说明文档以及标准化的Git协作模板ISSUE和PULL_REQUEST中文模板开箱即可导入IDE运行调试适合中小企业快速上线也适合作为Java全栈开发学习范例。1. 这不是又一个“Hello World”后台——它是一套真正能进办公室跑起来的人事系统你有没有试过在招聘网站上刷到“Java全栈开发工程师”岗位JD里写着“熟悉SpringBoot、MyBatis、Layui”然后点开GitHub想找个真实项目练手结果翻了十几页全是单模块Demo要么只有登录注册要么只做了个员工列表增删改查再配上一句“权限待完善”我试过而且踩过三次坑——第一次导入就报错Mapper扫描失败第二次配好数据库却卡在Layui表格渲染空白第三次好不容易跑通发现“财务专员”点工资模块居然能导出全部员工明细表……这种“半成品式学习资源”浪费的不只是时间更是对真实业务逻辑的理解机会。这套SpringBoot人事后台系统是我去年帮一家200人规模的制造企业做数字化升级时沉淀下来的基线版本。它不是教学玩具而是从HR小姐姐每天要填的《入职登记表》、考勤专员凌晨三点导出的《月度异常打卡汇总》、财务每月15号前必须锁定的《薪资核算单》里长出来的。它用SpringBoot 2.3.12.RELEASE兼容JDK8避开高版本兼容性雷区搭配MyBatis-Plus 3.4.3.4不是最新版但稳定压倒一切MySQL 5.7中小企业的事实标准前端没堆Vue或React就用最朴素的jQuery 3.6.0 Layui 2.8.18——不是技术保守而是因为Layui的模块化加载机制和内置的form.verify、table.render这些API能让一个刚毕业的实习生三天内就学会改考勤统计报表的导出逻辑。关键词里的“角色控制”不是指后台写死if-else判断role“admin”而是把权限规则彻底“数据库化”菜单是否显示、按钮是否禁用、数据能否查询全部由sys_menu、sys_role_menu、sys_role_button三张表动态驱动“SpringBoot权限”也不是简单套Shiro或Spring Security的默认拦截器而是基于MyBatis-Plus的LambdaQueryWrapper在Service层就完成数据级过滤——比如人事专员查档案SQL自动拼接AND dept_id #{currentDeptId}“Layui前端”更不是静态HTML套壳它的tab选项卡切换、layer.open弹窗传参、form.on提交事件绑定都和后端Controller的RequestBody、PathVariable形成严丝合缝的契约。整套系统跑起来后你打开浏览器开发者工具Network面板能看到每一个请求URL都带着清晰的业务语义/recruit/list、/attendance/export、/salary/compute而不是/api/v1/xxx?id123这种需要查文档才能猜的接口。如果你是中小企业IT负责人它能让你今天下载、明天部署、后天让HR开始录入2024届校招生简历如果你是Java初学者它会告诉你MyBatis-Plus的IService接口怎么封装通用分页逻辑Layui的table.render如何用done回调处理异步加载后的按钮权限渲染如果你是面试官你可以直接问候选人“如果现在要给‘普通用户’增加查看自己绩效评语的权限你要改哪三张表改完之后前端哪个JS文件需要同步更新为什么不能只改后端”——答案就在这个项目的src/main/java/com/example/person/service/impl/RoleMenuServiceImpl.java和static/js/modules/permission.js里。它不炫技但每行代码都在解决一个真实问题。2. 系统整体设计与权限架构拆解为什么选择“数据库驱动Layui轻量组合”2.1 不选Spring Security而用自研RBACABAC混合模型的底层逻辑很多教程一讲权限就甩出Spring Security的EnableWebSecurity配置一堆antMatchers看似高大上但落到企业实际场景里问题立刻暴露当财务总监突然要求“只允许查看本部门工资条且禁止导出Excel”这种细粒度的数据权限Security的URL拦截根本无能为力。我们最终采用RBAC基于角色的访问控制 ABAC基于属性的访问控制混合模型核心在于把权限决策从框架层下沉到业务层。RBAC部分负责“谁能看什么菜单、点什么按钮”通过四张基础表实现-sys_role存储管理员、财务专员等角色基础信息-sys_menu定义所有菜单项如“招聘管理”、“考勤统计”type字段区分menu菜单、button按钮、api接口-sys_role_menu角色与菜单的多对多关系控制左侧导航栏显示-sys_role_button角色与按钮的多对多关系控制页面内“新增”、“导出”等操作按钮显隐。ABAC部分则解决“能看到哪些数据”关键在sys_user表的dept_id部门ID和sys_role表的data_scope数据范围字段。例如人事专员的角色data_scope设为“DEPT”其查询员工档案时Service层会自动注入dept_id #{currentUserId.deptId}条件而管理员data_scope为“ALL”则跳过此过滤。这种设计避免了在每个Controller里硬编码if(role.equals(“admin”))而是通过统一的DataScopeInterceptor拦截器在MyBatis执行SQL前动态拼接WHERE条件。提示DataScopeInterceptor的实现要点在于获取当前登录用户信息。我们没用ThreadLocal存UserContext易内存泄漏而是通过Spring AOP切面在Controller方法执行前从SecurityContextHolder中提取Authentication对象并将其转换为自定义的LoginUser实体再注入到MyBatis的Executor中。这样既保证线程安全又避免了每次DAO调用都查库。2.2 Layui前端为何不升级Vue三个现实约束下的务实选择看到“Layui”这个词可能有同学皱眉“这都2024年了还用Layui”——但当你真正去一家传统制造业企业现场调研就会发现他们的IT运维人员平均年龄45岁日常维护的是Windows Server 2012IE11环境HR使用的电脑甚至还有Win7系统而新招的00后实习生培训三天就能上手修改Layui的form表单验证规则。这就是选择Layui的核心原因零构建、低门槛、强兼容。Layui的模块化机制layui.use([‘form’,’table’]…)天然适配Java Web的传统部署模式所有JS/CSS文件直接放在static目录下无需webpack打包Tomcat启动即用它的table.render({elem: ‘#demo’, url: ‘/recruit/list’})直接对接后端RESTful接口返回JSON格式{code: 0, msg: ‘’, data: […], count: 100}连数据格式都不用额外转换更重要的是Layui的layer弹窗、laydate日期选择器、laypage分页组件全部内置没有npm install依赖地狱。我们曾做过对比测试同样实现“点击考勤记录行弹出详情弹窗”Vue方案需引入Element UI、配置axios拦截器、处理跨域、写.vue单文件组件耗时约4小时Layui方案只需复制一段layer.open代码改下url路径和data参数15分钟搞定。注意Layui的“响应式”并非Bootstrap式的栅格系统而是通过CSS媒体查询js动态计算容器宽度实现。我们在static/css/layuimini.css里重写了media (max-width: 768px)下的.layui-side、.layui-body样式确保手机端滑动侧边栏时主内容区自动适配这点常被忽略却是移动端可用性的关键。2.3 MyBatis-Plus不是“银弹”但它是中小项目最稳的加速器MyBatis-Plus的AutoGenerator代码生成器常被诟病“生成代码臃肿”。但在这个项目里我们做了精准裁剪只生成Entity、Mapper、Mapper.xml、Service、ServiceImpl五类文件砍掉Controller和VO因Layui前端直接消费DTO。关键优化点在于自定义模板引擎替换FreeMarker为Velocity因其语法更简洁#if($table.comment)${table.comment}#end vs #if table.comment??${table.comment}/#if字段注释继承数据库字段comment自动转为JavaDoc如MySQL中name字段注释为“员工姓名”生成的Employee.java里就有/** 员工姓名 */ private String name;逻辑删除配置全局启用TableLogic所有delete操作转为UPDATE set deleted 1避免误删——这对人事系统至关重要离职员工档案必须保留审计痕迹。更值得强调的是MyBatis-Plus的条件构造器。比如绩效模块的“按部门考核周期筛选”传统写法要拼SQL字符串极易SQL注入而LambdaQueryWrapper可写成LambdaQueryWrapperPerformance wrapper Wrappers.lambdaQuery(); wrapper.eq(Performance::getDeptId, deptId) .ge(Performance::getStartDate, startDate) .le(Performance::getEndDate, endDate) .orderByDesc(Performance::getScore);编译期检查字段名运行时自动转义参数这才是真正的生产力提升。3. 核心模块实操解析从招聘入口到工资核算的完整链路3.1 招聘管理模块如何让“职位发布”变成可追踪的业务闭环招聘模块不是简单的CRUD它模拟了真实HR工作流职位发布 → 简历投递 → 初筛 → 面试安排 → 录用审批 → 入职登记。关键在于状态机设计和数据联动。数据库层面recruit_position表存储职位信息recruit_resume表存简历recruit_interview表管面试recruit_offer表管录用。它们通过status字段串联position.status“PUBLISHED”已发布→ resume.status“RECEIVED”已接收→ interview.status“SCHEDULED”已预约→ offer.status“APPROVED”已审批。每个状态变更都触发对应业务逻辑当offer.status更新为“APPROVED”自动向sys_user表插入一条新记录并同步生成员工档案employee_info、初始化考勤规则attendance_rule、预设绩效周期performance_cycle。前端Layui实现上我们没用单一页面堆砌所有功能而是按角色拆分视图- 管理员看到“职位池总览”用table.render展示各职位投递数、转化率- 人事专员进入“我的招聘”tab切换显示“待初筛”、“待面试”、“待录用”三个子表- 面试官登录后首页直接显示“今日面试安排”调用/interview/today接口返回带候选人姓名、应聘职位、面试时间、会议室编号的卡片列表。实操心得Layui表格的toolbar工具栏我们自定义了“批量初筛通过”按钮。点击后调用/resume/batchApprove接口后端用MyBatis-Plus的updateBatchById()批量更新但关键细节在于——必须先校验这批简历是否都处于“RECEIVED”状态否则抛出业务异常“存在非待处理简历”避免误操作。这个校验逻辑写在ServiceImpl的batchApprove()方法开头而非前端JS里因为前端可被绕过。3.2 考勤模块如何用MySQL函数搞定复杂排班与异常识别考勤是人事系统的“硬骨头”难点不在打卡记录存储而在排班规则解析和异常类型判定。我们放弃引入Quartz定时任务计算考勤全部用MySQL原生函数实现确保数据一致性且降低运维复杂度。核心表结构-attendance_schedule存储排班规则字段包括dept_id部门、work_days工作日如”1,2,3,4,5”、start_time上班时间、end_time下班时间、late_minutes迟到阈值-attendance_record打卡记录含user_id、check_date、check_in_time、check_out_time、statusNORMAL/ABSENT/LATE/EARLY/MISS。关键SQL逻辑藏在AttendanceMapper.xml的select标签里select idgetDailySummary resultTypemap SELECT u.name as userName, r.check_date, CASE WHEN r.check_in_time IS NULL THEN 缺勤 WHEN TIME(r.check_in_time) TIME(s.start_time) INTERVAL s.late_minutes MINUTE THEN 迟到 WHEN r.check_out_time IS NULL THEN 早退 ELSE 正常 END as statusDesc FROM attendance_record r JOIN sys_user u ON r.user_id u.id JOIN attendance_schedule s ON u.dept_id s.dept_id WHERE r.check_date #{date} /select这段SQL利用MySQL的TIME()函数和INTERVAL运算直接在数据库层完成迟到判定避免Java层循环计算。更妙的是work_days字段的解析我们用FIND_IN_SET(DAYOFWEEK(#{date}), s.work_days)判断当天是否为工作日若返回0则标记为“休息日”不参与考勤统计。注意事项MySQL的DAYOFWEEK()返回值是1周日到7周六而企业排班通常按周一至周五所以work_days字段存的是”2,3,4,5,6”。这个细节必须和HR确认清楚否则会导致周末打卡被错误计入。3.3 工资核算模块动态公式引擎如何替代硬编码薪资逻辑工资模块最怕“改需求”。财务说“本月起交通补贴从300元改为按职级发放”程序员就得改代码、测逻辑、发版本。我们设计了一个轻量级公式引擎把薪资计算规则从Java代码里剥离出来存入数据库。salary_formula表结构-formula_code公式编码如”SALARY_BASE”基本工资、”SUBSIDY_TRAFFIC”交通补贴-formula_content公式内容如”base_salary (job_level 1 ? 200 : job_level 2 ? 300 : 500)“-formula_desc中文描述“交通补贴初级200中级300高级500”。后端用Groovy脚本引擎解析公式// SalaryCalculationService.java public BigDecimal calculate(String formulaCode, MapString, Object params) { Binding binding new Binding(params); // 注入base_salary, job_level等变量 GroovyShell shell new GroovyShell(binding); Object result shell.evaluate(formulaContent); // 执行公式 return new BigDecimal(result.toString()); }这样当财务要调整补贴标准只需在后台管理界面修改salary_formula表的formula_content字段无需重启服务下次核算时自动生效。我们甚至预留了formula_type字段未来可扩展为“固定值”、“百分比提成”、“阶梯计件”等多种类型。实操陷阱Groovy引擎存在安全风险必须严格限制可执行函数。我们在shell.evaluate前用正则表达式校验formula_content只包含数字、字母、运算符、括号和预设变量名禁止出现System.exit()、new File()等危险调用。这是上线前必须做的安全加固。4. 四级角色权限落地实录从菜单配置到按钮渲染的全流程4.1 权限配置后台如何让非技术人员也能自主调整权限权限配置不是开发者的专利。我们专门开发了/system/role/permission页面让HRBP或IT主管能图形化操作。界面左侧树形展示所有菜单来自sys_menu右侧显示当前角色已分配的菜单和按钮。技术实现上Layui的tree组件绑定/menu/tree接口返回嵌套JSON[ {id:1,title:招聘管理,children:[ {id:2,title:职位管理,type:menu}, {id:3,title:简历管理,type:menu}, {id:4,title:面试安排,type:button} ]} ]用户勾选节点后前端收集选中的menuId和buttonId数组提交到/role/assignPermission。后端Service层执行两步操作1. 清空sys_role_menu中该角色的所有记录2. 批量插入新记录其中menuId对应菜单buttonId对应按钮buttonId为空时视为菜单权限。关键细节按钮权限typebutton在sys_menu表中不作为独立菜单存在而是作为父菜单的子项。例如“简历管理”菜单id3下“导出Excel”按钮id101的pid3。这样设计的好处是当用户有“简历管理”菜单权限时自动获得其下所有按钮权限若只想开放“导出”则单独分配buttonId101即可。这种父子关系在Layui tree渲染时通过spread: false控制子节点默认折叠避免界面混乱。4.2 前端按钮级权限控制Layui如何动态显示/隐藏操作按钮Layui本身不提供权限指令我们通过自定义属性jQuery遍历实现。所有按钮添加data-auth属性!-- 招聘列表页 -- button typebutton classlayui-btn layui-btn-sm>// static/js/modules/permission.js function initButtonAuth() { $.ajax({ url: /user/permissions, type: GET, success: function(res) { if (res.code 0) { var authList res.data; // [recruit:resume:export, recruit:resume:approve] $([data-auth]).each(function() { var authCode $(this).data(auth); if (!authList.includes(authCode)) { $(this).hide(); // 或 $(this).attr(disabled, true).addClass(layui-btn-disabled); } }); } } }); }/user/permissions接口返回当前用户拥有的所有权限编码列表前端遍历所有data-auth元素匹配后决定显隐。这里有个易错点按钮禁用disabled不等于不可见。对于“删除”这类高危操作我们选择hide()彻底隐藏而对于“编辑”则用disabled加灰显让用户知道功能存在但暂不可用避免困惑。4.3 数据权限实战财务专员为何只能看到本部门工资条数据权限的终极考验是让财务专员登录后点开“工资查询”列表里只显示自己部门员工的记录且导出按钮点击后生成的Excel也仅含本部门数据。实现分三步1.Controller层注入部门ID在SalaryController.list()方法中通过LoginUser LoginUser user参数由自定义注解解析获取当前用户部门ID2.Service层构造动态条件SalaryServiceImpl.list()方法接收deptId参数用LambdaQueryWrapper拼接java wrapper.eq(Salary::getDeptId, deptId); // 强制过滤3.Mapper层确保无绕过SalaryMapper.xml中所有SELECT语句都加上if testdeptId ! null and deptId ! AND dept_id #{deptId}/if双重保险。更关键的是导出功能。/salary/export接口接收deptId参数但前端无法伪造因deptId来自后端Session且导出逻辑在Service层完成直接调用salaryService.list(wrapper)获取数据再用Apache POI生成Excel。这样即使黑客篡改前端请求参数后端也会校验deptId合法性拒绝非法请求。实操心得我们曾遇到一个坑——财务专员A和B同属销售部但A是销售一部B是销售二部。最初设计只按dept_id过滤导致A能看到B的工资。解决方案是在sys_user表增加sub_dept_id子部门ID并将数据权限粒度细化到子部门级。这个细节提醒我们权限设计必须深入业务组织架构不能停留在“部门”这一层抽象。5. 部署与协作规范为什么Git模板比代码更重要5.1 标准化Git协作模板ISSUE和PULL_REQUEST如何减少沟通成本项目根目录下的ISSUE_TEMPLATE.zh-CN.md和PULL_REQUEST_TEMPLATE.zh-CN.md不是摆设而是团队协作的“宪法”。我们强制要求ISSUE模板必须填写问题类型Bug/Feature/Question复现步骤精确到点击哪个菜单、输入什么参数期望结果与实际结果附截图环境信息Chrome 120 / MySQL 5.7.32。PULL_REQUEST模板必须包含关联ISSUE编号如“Fixes #123”修改说明“修复考勤导出Excel时周末日期显示为0000-00-00的问题”测试验证“已测试1. 正常工作日导出2. 周末导出3. 跨月导出”影响范围“仅修改AttendanceExportService.java不影响其他模块”。这套模板让代码审查效率提升50%。以前Review一个PR要花20分钟问“你改这儿干啥”现在直接看模板就知道上下文以前Bug复现要反复追问“你用的什么浏览器”现在模板里已明确标注。5.2 一键部署脚本如何让非Java人员也能部署系统README.md里不止有“mvn clean package”还有针对不同环境的部署指南-开发环境./scripts/dev-start.sh自动启动H2内存数据库加载test-data.sql初始化测试数据-生产环境./scripts/prod-deploy.sh检查MySQL连接、创建数据库、执行schema.sql和data.sql、启动jar包并输出日志路径。脚本核心逻辑用bash实现#!/bin/bash # scripts/prod-deploy.sh DB_HOST192.168.1.100 DB_NAMEperson_db echo 正在检查MySQL连接... mysql -h $DB_HOST -u root -p123456 -e use $DB_NAME; /dev/null 21 if [ $? -ne 0 ]; then echo MySQL未就绪正在初始化... mysql -h $DB_HOST -u root -p123456 sql/schema.sql mysql -h $DB_HOST -u root -p123456 $DB_NAME sql/data.sql fi echo 正在启动应用... nohup java -jar target/person-admin.jar --spring.profiles.activeprod logs/app.log 21 echo 应用已启动日志查看tail -f logs/app.log这个脚本让运维同事不用懂Java只要会敲sh scripts/prod-deploy.sh就能完成部署。我们甚至为它写了中文注释连/dev/null 21都解释为“把命令执行的提示信息暂时藏起来避免干扰”。5.3 常见问题排查速查表那些让你熬夜的坑我们都趟过了问题现象可能原因排查步骤解决方案Layui表格显示“暂无数据”但Network里接口返回正常JSON前端table.render的response配置未匹配后端返回格式1. 打开浏览器Console2. 查看Layui报错“data is not defined”3. 检查response配置是否为{data: data, count: count}在table.render配置中显式指定response: {data: data, count: count}因后端返回格式为{code:0, msg:’‘, data:[…], count:100}登录后菜单栏空白但/user/menu接口返回正常数据Layui tree组件未正确解析嵌套JSON1. 查看Network中/user/menu返回的JSON2. 确认是否有children字段3. 检查tree的id和pid字段名是否与后端一致在tree.render配置中设置idName: id,pidName: pid,childrenName: children确保字段映射正确MyBatis-Plus分页插件失效SQL无LIMIT语句PageHelper与MyBatis-Plus分页插件冲突1. 检查pom.xml是否同时引入pagehelper-spring-boot-starter2. 查看启动日志是否有“PageInterceptor registered”字样移除PageHelper依赖只用MyBatis-Plus的IPage因后者已内置分页拦截器二者共存会导致拦截器重复执行财务专员导出工资Excel内容为空白Apache POI版本与JDK兼容性问题1. 查看logs/app.log是否有java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook2. 检查pom.xml中poi版本将poi版本从5.2.4降级为4.1.2因高版本POI需JDK11而项目基于JDK8独家避坑技巧Layui的form.verify自定义验证规则容易因作用域问题失效。正确写法是在layui.use([form], function(){...})内部定义而非全局window对象下。我们曾因此浪费3小时最后发现是JS文件加载顺序导致form模块未就绪就执行了verify注册。6. 学习与扩展建议从“能跑”到“能用”的进阶路径如果你是初学者拿到这个项目别急着改代码。先做三件事第一用admin/admin登录把七大模块每个按钮都点一遍记下哪些操作会弹窗、哪些会跳转、哪些会导出文件第二打开MySQL客户端查sys_role、sys_menu、sys_role_menu三张表手动修改一条记录比如把财务专员的sys_role_menu中工资模块的menu_id删掉再刷新页面看菜单是否消失第三找到src/main/resources/application-prod.yml把spring.datasource.url改成你本地MySQL地址执行mvn clean package用java -jar启动感受真实部署流程。如果你想把它变成自己的项目推荐三个安全的扩展方向一是增加短信通知集成用阿里云短信SDK在录用审批通过后自动发送短信给候选人二是实现Excel模板导入在招聘模块让HR能下载空白模板填好后上传批量导入简历三是开发移动端H5页面用Layui Mobile重构考勤打卡页适配微信内置浏览器。这三个方向都不涉及核心权限架构改动却能极大提升实用性。最后分享一个小技巧项目里的9Z46kHJQdUKw9NMshznS-master-eb8c7f524fae0718428256f40f1a6fb5ebbba957目录其实是GitHub Actions自动打包的产物里面包含编译好的jar包和配置文件。如果你不想装Maven直接把这个目录里的person-admin.jar拷贝到服务器修改application-prod.yml执行java -jar person-admin.jar就能跑起来——这才是真正意义上的“开箱即用”。我在客户现场演示时就是从这个目录拖出jar包5分钟完成部署客户经理当场拍板采购。技术的价值从来不在多炫酷而在多可靠。本文还有配套的精品资源点击获取简介这个人事管理后台系统用SpringBoot 2.x做后端MyBatis-Plus操作MySQL前端基于jQuery和Layui实现响应式界面。功能覆盖招聘管理、员工档案、考勤记录、薪资核算、绩效评估、培训安排、系统设置七大业务板块每个模块都支持实际业务流程操作。权限体系分四级管理员、财务专员、人事专员、普通用户预设账号密码如admin/admin、cw/cw等登录即用。权限控制细化到菜单和按钮级别所有权限规则存于数据库修改后实时生效不用重启服务。项目结构规范包含完整Java源码src目录、Maven配置pom.xml、详细README说明文档以及标准化的Git协作模板ISSUE和PULL_REQUEST中文模板开箱即可导入IDE运行调试适合中小企业快速上线也适合作为Java全栈开发学习范例。本文还有配套的精品资源点击获取