1. 项目概述:前后端分离架构的人力资源管理系统
这个基于SpringBoot+Vue+MyBatis+MySQL技术栈的人力资源管理系统,是我在2022年为某中型企业开发的实际项目。当时客户需要一个能支持300人规模、具备完整组织架构管理功能的系统,同时要求开发周期控制在两个月内。前后端分离的架构选择,正是为了应对这种既要快速交付又要保证可维护性的典型场景。
你可能已经注意到,市面上类似的开源项目不少,但大多数要么功能过于简单,要么架构设计不合理。我们这个方案经过实际生产环境验证,在权限控制、数据一致性、性能优化等方面都有独特设计。比如在部门树形结构展示上,我们采用了Vue的虚拟滚动技术,即使面对500+节点的超深层级也能保持流畅操作。
提示:选择前后端分离架构时,一定要考虑团队的技术储备。如果团队没有Vue或React经验,强行上马反而会拖慢进度。
系统主要包含六大模块:
- 组织架构管理(部门树、岗位设置)
- 员工信息管理(入职到离职全生命周期)
- 考勤管理(支持多种打卡方式对接)
- 薪酬计算(与考勤数据联动)
- 绩效考核(KPI模板配置)
- 系统管理(RBAC权限体系)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型与架构设计
2.1 为什么选择这些技术组合?
SpringBoot + Vue + MyBatis这个"铁三角"组合,在2023年仍然是企业级应用的最佳选择之一。我们做过压力测试,在4核8G的服务器上,这个架构可以轻松支撑每秒300+的并发请求。
后端技术栈的考量:
- SpringBoot 2.7.x:相比原生Spring,它简化了配置且内嵌Tomcat
- MyBatis 3.5.x:在复杂SQL场景下比JPA更灵活
- MySQL 8.0:支持JSON字段和窗口函数等高级特性
- Redis 6.x:用于会话管理和热点数据缓存
前端技术栈的亮点:
- Vue 3.x组合式API:比Options API更利于代码组织
- Element Plus:国内最成熟的中后台UI组件库
- Axios:封装了自动重试和请求拦截的增强版本
- Vue Router:实现了动态路由和路由守卫
2.2 前后端分离的通信设计
系统采用JWT作为认证方案,但做了几点关键改进:
- 双Token机制:access_token(30分钟过期) + refresh_token(7天过期)
- 指纹绑定:将用户设备指纹加入Token签名
- 黑名单:注销的Token会进入Redis黑名单
API设计遵循以下规范:
javascript复制// 成功响应
{
"code": 200,
"data": {...},
"message": "success"
}
// 错误响应
{
"code": 500,
"data": null,
"message": "具体错误信息"
}
3. 核心功能实现细节
3.1 组织架构树的实现
部门树是HR系统的核心功能,我们采用了闭包表(Closure Table)存储方案。相比传统的邻接表,它在查询效率上有显著优势。
MySQL表设计:
sql复制CREATE TABLE `sys_dept` (
`id` bigint NOT NULL COMMENT '部门ID',
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '部门名称',
`sort` int DEFAULT '0' COMMENT '排序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `sys_dept_relation` (
`ancestor` bigint NOT NULL COMMENT '祖先节点',
`descendant` bigint NOT NULL COMMENT '后代节点',
`depth` int DEFAULT NULL COMMENT '深度',
PRIMARY KEY (`ancestor`,`descendant`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
前端使用Vue的递归组件实现树形展示,关键代码如下:
vue复制<template>
<el-tree
:data="deptTree"
node-key="id"
:props="defaultProps"
:expand-on-click-node="false"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<el-button type="text" @click.stop="append(data)">添加</el-button>
<el-button type="text" @click.stop="remove(node, data)">删除</el-button>
</span>
</span>
</template>
</el-tree>
</template>
3.2 动态权限控制方案
系统采用RBAC模型,但增加了数据权限控制。比如:部门经理只能看到本部门员工数据。
权限表关键设计:
sql复制CREATE TABLE `sys_role` (
`id` bigint NOT NULL COMMENT '角色ID',
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
`data_scope` int DEFAULT '1' COMMENT '数据范围(1:全部 2:自定义 3:本部门 4:本部门及以下 5:仅本人)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
在MyBatis层面,我们通过拦截器自动添加数据权限SQL:
java复制@Intercepts({
@Signature(type= StatementHandler.class, method="prepare", args={Connection.class, Integer.class})
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前用户的数据权限范围
Integer dataScope = SecurityUtils.getDataScope();
if (dataScope != null && dataScope == 3) {
// 拼接本部门数据过滤条件
String deptFilter = " AND dept_id = " + SecurityUtils.getDeptId();
// 修改原始SQL...
}
return invocation.proceed();
}
}
4. 部署与运维实践
4.1 生产环境部署方案
我们推荐使用Docker Compose进行一键部署,这是我们的docker-compose.yml示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
container_name: hrms-mysql
environment:
MYSQL_ROOT_PASSWORD: your_strong_password
MYSQL_DATABASE: hrms
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
restart: always
redis:
image: redis:6-alpine
container_name: hrms-redis
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
restart: always
backend:
build: ./backend
container_name: hrms-backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
SPRING_PROFILES_ACTIVE: prod
restart: always
frontend:
build: ./frontend
container_name: hrms-frontend
ports:
- "80:80"
restart: always
4.2 常见部署问题解决
-
前端路由404问题:
在Nginx配置中添加try_files指令:nginx复制location / { try_files $uri $uri/ /index.html; } -
MySQL时区问题:
在SpringBoot配置中添加:properties复制spring.datasource.url=jdbc:mysql://localhost:3306/hrms?useSSL=false&serverTimezone=Asia/Shanghai -
Vue生产环境跨域:
开发环境用webpack代理,生产环境用Nginx反向代理:nginx复制location /api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; }
5. 性能优化实战经验
5.1 MyBatis二级缓存陷阱
我们曾经因为不当使用MyBatis二级缓存导致数据不一致。最终采用的方案是:
- 对配置类数据开启二级缓存
- 对业务数据只用一级缓存
- 所有update/insert/delete操作后手动清除相关缓存
配置示例:
xml复制<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
5.2 Vue组件性能优化
员工列表页在渲染1000+条数据时出现卡顿,我们通过以下手段优化:
- 虚拟滚动:使用vue-virtual-scroller组件
- 数据分片:每次只渲染可视区域附近的数据
- 冻结列:固定左侧列使用CSS transform而非重新渲染
优化后代码结构:
vue复制<RecycleScroller
class="employee-list"
:items="employees"
:item-size="56"
key-field="id"
v-slot="{ item }"
>
<EmployeeRow :employee="item" />
</RecycleScroller>
6. 安全防护措施
6.1 XSS防御方案
除了常规的转义输出外,我们还特别处理了富文本内容:
- 前端使用DOMPurify过滤
- 后端用Jsoup进行二次验证
- 对PDF导出内容进行特殊处理
SpringBoot中的配置:
java复制@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'");
}
}
6.2 SQL注入防护
虽然MyBatis的#{}已经可以防止大部分注入,但我们额外做了:
- 字段白名单校验
- 动态表名参数校验
- 定期SQL审计
示例校验器:
java复制public class SqlInjectionValidator {
private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$");
public static void validateTableName(String tableName) {
if (!PATTERN.matcher(tableName).matches()) {
throw new IllegalArgumentException("Invalid table name");
}
}
}
7. 项目源码结构与开发规范
7.1 后端项目结构
code复制hrms-backend
├── src/main/java
│ ├── com/example/hrms
│ │ ├── common/ # 通用组件
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制层
│ │ ├── entity/ # 实体类
│ │ ├── mapper/ # MyBatis映射
│ │ ├── service/ # 业务逻辑
│ │ └── HrmsApplication.java
│ └── resources
│ ├── mapper/ # XML映射文件
│ ├── static/ # 静态资源
│ ├── templates/ # 模板文件
│ ├── application.yml
│ └── application-prod.yml
7.2 前端项目规范
-
命名约定:
- 组件:PascalCase (如EmployeeList.vue)
- 变量:camelCase
- 常量:UPPER_CASE
-
API调用规范:
javascript复制// 在src/api/employee.js中集中管理API
import request from '@/utils/request'
export function listEmployees(params) {
return request({
url: '/api/employees',
method: 'get',
params
})
}
- 状态管理:
使用Pinia替代Vuex,更简洁的类型支持:
javascript复制// stores/employee.js
import { defineStore } from 'pinia'
export const useEmployeeStore = defineStore('employee', {
state: () => ({
currentEmployee: null,
departmentEmployees: []
}),
actions: {
async fetchDepartmentEmployees(deptId) {
const { data } = await listEmployees({ deptId })
this.departmentEmployees = data
}
}
})
8. 扩展与二次开发建议
8.1 与第三方系统集成
-
钉钉/企业微信对接:
- 使用官方SDK实现单点登录
- 同步组织架构到HR系统
- 对接考勤打卡数据
-
电子合同签署:
- 集成e签宝或法大大等平台
- 自动生成劳动合同
- 签署状态回调处理
8.2 微服务化改造
当系统需要支持5000+用户时,建议拆分为:
- 认证服务(单独部署)
- 员工基础服务
- 考勤计算服务
- 薪酬计算服务
使用Spring Cloud Alibaba组件:
yaml复制# Nacos配置示例
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
8.3 数据分析扩展
使用Elasticsearch实现员工数据全文检索:
java复制@Repository
public interface EmployeeSearchRepository extends ElasticsearchRepository<Employee, Long> {
List<Employee> findByNameOrPhoneOrEmail(String name, String phone, String email);
}
构建数据看板:
- 使用ECharts实现可视化
- 定时任务生成日报
- 异常数据自动预警
这个项目从技术选型到最终上线,我们团队积累了丰富的实战经验。特别是在权限控制和数据一致性方面,趟过了不少坑。建议初次接触此类系统的开发者,先从核心模块入手,逐步扩展功能。
