1. 项目概述:当人事管理遇上SpringBoot+Vue
每次看到中小企业用Excel表格管理员工档案时,我都忍不住想给他们安利这个技术方案。这个基于SpringBoot+Vue的人事管理系统,本质上是用前后端分离架构解决传统HR管理的三大痛点:数据分散(员工信息在多个Excel里)、流程割裂(入职离职要跑多个部门)、统计困难(月底算考勤要加班)。
技术栈选择上,后端用SpringBoot不是因为它流行,而是看中其"约定优于配置"的特性——中小企业往往没有专职运维,一个内嵌Tomcat的jar包扔服务器上就能跑。前端选Vue.js则是因为其渐进式特性,从最简单的员工信息表开始,后续可以逐步添加绩效模块、培训系统,而不用推翻重来。数据库用MySQL更是中小企业标配,免费、够用、运维简单。
这个项目特别适合作为计算机专业学生的毕业设计或课程设计,因为它:
- 覆盖了CRUD基础功能(员工信息管理)
- 包含典型业务逻辑(考勤计算、薪资核算)
- 涉及关键技术点(JWT认证、Excel导入导出)
- 具备可扩展性(后期可加OA审批流)
提示:系统默认包含的部门管理、职位管理、员工档案等模块,已经能支撑20-200人规模企业的基本人事管理需求。超过200人时需要考虑分库分表,但这属于进阶优化范畴。
2. 核心模块设计与技术实现
2.1 后端SpringBoot架构解析
用IDEA新建SpringBoot项目时,我习惯按功能而非层级分包(传统dao/service/controller分法会导致频繁跨包操作)。典型包结构如下:
code复制com.hrsystem
├── config # 安全配置、Swagger配置
├── constant # 状态码枚举、权限常量
├── controller # 按模块细分:auth/employee/dept等
├── dto # 数据传输对象
├── entity # JPA实体类
├── exception # 自定义异常
├── repository # JPA仓储接口
├── service # 业务逻辑实现
├── util # 工具类
└── vo # 视图对象
数据库设计遵循第三范式的同时,也需要为性能适当冗余。比如员工表(employee)核心字段:
sql复制CREATE TABLE `employee` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '姓名',
`gender` char(1) DEFAULT '男' COMMENT '性别',
`id_card` char(18) UNIQUE COMMENT '身份证号',
`entry_date` date NOT NULL COMMENT '入职日期',
`position_id` bigint COMMENT '职位ID',
`dept_id` bigint COMMENT '部门ID',
`salary` decimal(10,2) COMMENT '基本工资',
`status` tinyint DEFAULT 1 COMMENT '状态(1在职 2离职)',
PRIMARY KEY (`id`),
FOREIGN KEY (`dept_id`) REFERENCES `department` (`id`),
FOREIGN KEY (`position_id`) REFERENCES `position` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 前端Vue.js工程化实践
前端采用Vue CLI搭建的工程,关键配置项:
vue.config.js中设置devServer代理,解决跨域问题- 使用Vuex管理全局状态(如用户登录信息)
- 按功能划分路由,启用懒加载提升性能
典型页面组件结构:
code复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── HrPagination.vue # 分页组件
│ └── HrUpload.vue # 文件上传组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
└── views/
├── employee/ # 员工管理
├── system/ # 系统管理
└── auth/ # 认证相关
2.3 前后端交互关键点
RESTful API设计遵循以下规范:
- GET /employees - 获取员工列表
- POST /employees - 新增员工
- PUT /employees/{id} - 修改员工信息
- DELETE /employees/{id} - 删除员工
使用Spring Security + JWT实现认证流程:
- 前端提交用户名密码到/auth/login
- 后端校验通过后生成JWT返回
- 前端后续请求在Header中加入Authorization: Bearer
- 后端通过JwtFilter校验令牌有效性
注意:开发环境下可以把token过期时间设为7天,生产环境建议2小时,并通过refreshToken机制实现无感刷新。
3. 典型业务场景实现
3.1 员工入职全流程开发
从收到Offer到生成工号的全流程代码实现:
java复制// EmployeeController.java
@PostMapping
public Result addEmployee(@Valid @RequestBody EmployeeDTO dto) {
// 1. 校验身份证号唯一性
if (employeeService.existsByIdCard(dto.getIdCard())) {
throw new BusinessException(ErrorCode.ID_CARD_EXIST);
}
// 2. 设置初始密码(身份证后6位)
String initPassword = dto.getIdCard().substring(12);
dto.setPassword(DigestUtils.md5DigestAsHex(initPassword.getBytes()));
// 3. 生成工号(年份+部门代码+序号)
String employeeId = employeeService.generateEmployeeId(dto.getDeptId());
dto.setEmployeeId(employeeId);
// 4. 保存到数据库
return Result.success(employeeService.addEmployee(dto));
}
前端表单验证逻辑:
javascript复制// EmployeeForm.vue
const rules = {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
idCard: [
{ required: true, message: '请输入身份证号', trigger: 'blur' },
{ pattern: /^\d{17}[\dXx]$/, message: '身份证格式不正确' }
],
entryDate: [{
validator: (_, value, callback) => {
if (new Date(value) > new Date()) {
callback(new Error('入职日期不能晚于今天'))
} else {
callback()
}
}
}]
}
3.2 考勤统计与薪资计算
考勤模块核心在于状态机设计:
java复制public enum AttendanceStatus {
NORMAL(0, "正常"),
LATE(1, "迟到"),
EARLY(2, "早退"),
ABSENT(3, "缺勤"),
LEAVE(4, "请假");
// 计算应扣工资
public BigDecimal getDeduction(BigDecimal dailySalary) {
return switch (this) {
case LATE -> dailySalary.multiply(new BigDecimal("0.2"));
case EARLY -> dailySalary.multiply(new BigDecimal("0.3"));
case ABSENT -> dailySalary;
default -> BigDecimal.ZERO;
};
}
}
薪资核算Service关键代码:
java复制public Payroll calculatePayroll(Long employeeId, int year, int month) {
// 获取基本工资
BigDecimal baseSalary = employeeRepository.findBaseSalaryById(employeeId);
// 查询当月考勤记录
List<Attendance> records = attendanceRepository
.findByEmployeeIdAndYearAndMonth(employeeId, year, month);
// 计算应扣款项
BigDecimal deduction = records.stream()
.map(r -> r.getStatus().getDeduction(baseSalary.divide(new BigDecimal(22), 2)))
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 生成薪资单
Payroll payroll = new Payroll();
payroll.setBaseSalary(baseSalary);
payroll.setDeduction(deduction);
payroll.setNetSalary(baseSalary.subtract(deduction));
return payroll;
}
4. 项目部署与运维实战
4.1 生产环境部署方案
推荐使用Docker Compose一键部署:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./hr-system-backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./hr-system-frontend
ports:
- "80:80"
volumes:
mysql_data:
后端关键部署配置:
properties复制# application-prod.properties
spring.datasource.url=jdbc:mysql://mysql:3306/hr_system?useSSL=false
spring.datasource.username=root
spring.datasource.password=${DB_PASSWORD}
# 启用生产环境性能优化
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
4.2 常见问题排查指南
问题1:前端打包后访问接口404
- 检查nginx配置是否正确代理API请求:
nginx复制location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
}
问题2:JWT令牌过期后无法刷新
- 确保刷新令牌接口(/auth/refresh)不在认证拦截路径内
- 前端应在响应拦截器中自动处理401错误:
javascript复制service.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
return refreshToken().then(() => {
return service(error.config)
})
}
return Promise.reject(error)
}
)
问题3:批量导入员工时性能差
- 使用Spring Batch优化批量处理:
java复制@Bean
public Step importEmployeeStep() {
return stepBuilderFactory.get("importEmployeeStep")
.<EmployeeDTO, Employee>chunk(100)
.reader(excelItemReader())
.processor(employeeItemProcessor())
.writer(employeeItemWriter())
.build();
}
5. 项目扩展方向建议
5.1 功能扩展
-
集成钉钉/企业微信:实现考勤打卡数据同步
- 调用平台开放API获取打卡记录
- 设计数据映射规则(如钉钉userId→系统employeeId)
-
电子合同签署:接入e签宝等第三方服务
- 使用SDK生成劳动合同模板
- 员工通过短信链接在线签字
-
BI可视化:集成ECharts展示人力结构
javascript复制// 部门人数饼图 const option = { tooltip: { trigger: 'item' }, series: [{ type: 'pie', data: deptData.map(d => ({ value: d.count, name: d.deptName })) }] }
5.2 性能优化
-
二级缓存:对频繁访问的组织架构数据启用Redis缓存
java复制@Cacheable(value = "dept", key = "#root.methodName") public List<Department> getAllDepartments() { return departmentRepository.findAll(); } -
数据库读写分离:使用Sharding-JDBC实现
yaml复制spring: shardingsphere: datasource: names: master,slave master: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://master:3306/hr_system slave: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://slave:3306/hr_system rules: replica-query: data-sources: pr_ds: primary-data-source-name: master replica-data-source-names: slave -
前端性能优化:
- 使用v-lazy延迟加载图片
- 对大型表格应用虚拟滚动
- 配置Webpack分包策略
这个项目我在实施过程中最大的体会是:技术方案必须匹配企业实际发展阶段。对于50人以下的公司,甚至可以先只用Vue+Excel实现基础信息管理,等规模扩大后再引入SpringBoot后端。过度设计带来的复杂度,往往比功能不足危害更大。