1. 项目概述
这个基于SpringBoot的人力资源管理系统(项目编号:project16186)是我去年带队为一家中型制造企业开发的内部管理平台。当时客户面临的主要问题是:员工信息分散在多个Excel表中,考勤数据与薪资计算脱节,部门间协作效率低下。经过三个月的开发迭代,我们最终交付的系统整合了六大核心模块,实现了从招聘到离职的全生命周期管理。
提示:在开始开发这类管理系统前,务必要用至少两周时间深入业务部门调研,我们最初就因低估了制造业排班复杂度而不得不返工考勤模块。
2. 系统架构设计
2.1 技术栈选型
核心采用SpringBoot 2.7 + MyBatis-Plus组合,前端使用Vue3+Element Plus。选择这套技术栈主要基于以下考量:
- 快速开发需求:SpringBoot的自动配置特性让团队能快速搭建RESTful API,比如员工模块的CRUD接口仅用2天就完成了基础开发
- 制造业特殊场景:MyBatis-Plus的Lambda查询很好地支持了复杂条件筛选,如车间工人的多维度排班查询
- 前后端分离:采用Vue3的Composition API编写可复用业务逻辑,如薪资计算组件被5个模块调用
数据库选用MySQL 8.0,关键配置如下:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/hr_system?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 加密处理
hikari:
maximum-pool-size: 20 # 根据并发测算设置
2.2 模块化设计
系统采用六边形架构划分业务边界,核心模块包括:
| 模块名称 | 主要功能 | 技术实现要点 |
|---|---|---|
| 组织架构 | 部门/岗位树形管理 | 使用MP的@TableField(typeHandler = JacksonTypeHandler)处理JSON结构 |
| 员工管理 | 全生命周期信息维护 | 采用策略模式处理不同用工类型(正式/劳务派遣) |
| 考勤系统 | 异常考勤智能识别 | 基于Quartz的弹性排班调度器 |
| 薪资核算 | 自动计算个税社保 | 规则引擎Drools实现多地社保政策适配 |
| 招聘管理 | 从邀约到Offer全流程 | 集成阿里云短信服务 |
| 报表中心 | 多维数据分析 | EasyExcel动态列导出 |
3. 核心功能实现
3.1 动态权限控制
采用RBAC模型实现细粒度权限管理,核心表关系如下:
sql复制CREATE TABLE `sys_role` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`role_name` varchar(30) NOT NULL COMMENT '角色名称',
`data_scope` tinyint DEFAULT '1' COMMENT '数据范围(1:全部 2:本部门 3:自定义)'
);
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID'
);
在Spring Security配置中,我们重写了hasPermission方法实现数据权限过滤:
java复制@Override
protected boolean preAuthorize(String permission) {
// 获取当前用户数据权限范围
Integer dataScope = SecurityUtils.getDataScope();
if (dataScope == DATA_SCOPE_DEPT) {
String deptId = SecurityUtils.getDeptId();
// 自动注入部门查询条件
Criteria.where("dept_id").eq(deptId);
}
return super.preAuthorize(permission);
}
3.2 考勤异常检测
制造业的考勤复杂点在于:
- 产线工人存在早中晚三班倒
- 不同工序的休息时间不固定
- 节假日需要特殊排班
我们设计的检测算法流程:
- 基础校验(打卡时间是否在班次内)
- 关联校验(是否提交请假/出差审批)
- 智能补偿(15分钟内打卡视为正常)
- 人工复核标记
关键代码片段:
java复制public AttendanceResult checkAbnormal(AttendanceRecord record) {
// 获取员工当月排班规则
ScheduleRule rule = ruleMapper.selectByUser(record.getUserId());
// 计算理论工作时间段
TimeRange workRange = calculateWorkRange(rule, record.getWorkDate());
// 三重校验逻辑
if (!workRange.contains(record.getClockTime())) {
if (!leaveService.existsApprovedLeave(record)) {
if (!compensatePolicy.apply(record)) {
return AttendanceResult.abnormal("缺勤");
}
}
}
return AttendanceResult.normal();
}
4. 性能优化实践
4.1 薪资计算加速
初期全量计算2000名员工薪资需要8分钟,优化后降至35秒,主要措施:
- 批量处理代替循环:将逐条SQL改为批量操作
java复制// 优化前
for (Employee emp : employees) {
salaryMapper.insert(emp);
}
// 优化后
salaryMapper.insertBatchSomeColumn(employees);
- 并行流计算:利用多核CPU并行处理非依赖计算
java复制List<SalaryItem> items = employees.parallelStream()
.map(this::calculateSingle)
.collect(Collectors.toList());
- Redis缓存:将不变的社保基数等数据放入缓存
java复制@Cacheable(value = "insurance", key = "#cityCode")
public BigDecimal getInsuranceRate(String cityCode) {
return insuranceMapper.selectRate(cityCode);
}
4.2 报表导出优化
当导出万人规模的考勤明细时,我们遇到了内存溢出问题。最终方案:
- 采用EasyExcel的异步导出模式
- 每500条数据刷新一次磁盘
- 前端轮询导出进度
核心配置:
java复制ExcelWriter writer = EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new PageWriteHandler(500))
.build();
WriteSheet sheet = EasyExcel.writerSheet("考勤明细")
.head(AttendanceExportVO.class)
.build();
// 分页查询数据并追加
for (int i = 0; i < pageCount; i++) {
writer.write(queryPage(i), sheet);
}
5. 踩坑与解决方案
5.1 日期处理陷阱
在考勤跨日计算时,我们最初用LocalDate导致23:00-07:00的夜班被错误分割。最终采用时间轴算法:
java复制// 正确计算跨日工作时长
Duration between = Duration.between(
start.atDate(workDate),
end.atDate(end.isBefore(start) ? workDate.plusDays(1) : workDate)
);
5.2 并发更新冲突
薪资审批时出现多人同时修改状态的问题,通过乐观锁解决:
sql复制UPDATE salary_bill
SET status = #{status},
version = version + 1
WHERE id = #{id} AND version = #{version}
对应的MyBatis-Plus实体类添加注解:
java复制@Version
private Integer version;
6. 部署实践建议
对于生产环境部署,我们总结出以下经验:
- JVM参数调优:
bash复制java -jar -Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
hr-system.jar
- Nginx静态资源缓存:
nginx复制location /static {
expires 365d;
add_header Cache-Control "public";
}
- 数据库连接池监控:
java复制@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setConnectionTimeout(30000);
config.setLeakDetectionThreshold(60000); // 泄漏检测
return new HikariDataSource(config);
}
这个项目让我深刻体会到,HR系统真正的挑战不在于技术实现,而在于对劳动法规、企业管理制度、行业特殊规则的理解。比如我们为制造业客户增加的"工时银行"功能,就需要同时考虑法律规定的月加班上限和企业的生产旺季需求。