☰
基于SpringBoot+Vue的中小企业人事管理系统设计与实现
2026/10/20 16:54:09 网站建设 项目流程

1. 为什么中小企业需要一套自己的“人事管理系统”

做这个项目之前,我先说个现象。很多中小型公司,员工规模从几十人到两三百人不等,人事管理基本靠Excel表格、微信消息、纸质审批单凑合着跑。员工入职填一张纸质登记表——录入HR的电脑——月底财务照着表格算工资——考勤情况靠部门主管月底口头汇报一声。表面上看"能用",但你真去问HR,她一定有一肚子苦水:员工信息散落在好几个版本的Excel里,改了一个忘记同步另一个;请假审批在微信里聊完了,月底对不上数;薪资变动没有留痕,员工来问工资构成,解释半天也没依据。

我做过好几套类似的系统,这套"基于SpringBoot+Vue的中小企业人事管理系统"算是其中功能覆盖比较完整的一套。技术栈就是标题里写的:后端SpringBoot做接口服务,前端Vue做管理页面,MySQL存数据,MyBatis负责数据库访问,源码里已经把登录认证、员工管理、部门管理、岗位管理、考勤管理、薪资管理、公告管理这些模块都实现了。如果你是做Java开发的大学生、刚转行的初级工程师,或者公司HR想要一套能内部跑起来的人事工具,这篇文章值得看完。我会把项目的模块设计、数据库表结构、核心代码写法、部署方式全部拆开讲,包括源码里那些"光看代码看不出来"的设计取舍。

为什么我强调"中小企业"这个限定词?因为大企业人事系统通常是saas平台或者定制化EHR系统,功能极其庞杂——绩效、培训、招聘、社保公积金、薪酬套账、组织架构调整留痕,光权限模型就能写几百页文档。但中小企业的诉求完全不一样:数据要集中、流程要简单、操作要直观、成本要可控。用企业微信+Excel不是不行,但数据孤岛严重,每次统计都是重复劳动;直接用SaaS人事平台,按人头收费,几十人一年下来也要几千块,还未必符合自己公司的管理习惯。

这套系统的定位就是:一个普通Java开发者在1-2周内能完成开发、能部署在内网服务器上、业务逻辑符合大多数中小企业管理习惯的人事管理系统。功能不求大而全,但求每个模块都能真正用起来。下面我从设计思路到落地细节,把整个项目从头到尾讲一遍。

2. 技术选型的底层逻辑:这套组合为什么能打

2.1 SpringBoot:中小型管理系统最省心的后端底座

后端选SpringBoot,在Java技术圈基本不用犹豫。相比传统的SSH(Spring+Struts+Hibernate)或者Spring MVC的XML配置时代,SpringBoot最大的价值是把"配置地狱"变成了"约定优于配置"。你新建一个项目,引入spring-boot-starter-web,写一个@RestController,一个main方法启动——一个能跑起来的HTTP服务就完成了。这对中小企业管理系统来说太重要了,因为这类系统的复杂度主要在业务逻辑上,而不是在框架配置上。

具体到人事系统,SpringBoot带来的直接好处有三个:

第一,内嵌Tomcat,部署成本低。以前部署一个JavaWeb项目,要单独装Tomcat、配置数据源、扔war包。SpringBoot直接打成可执行jar包,服务器上装个JDK,java -jar就能跑。中小企业没有专职运维,这种部署方式几乎不考验IT基础。

第二,Starter生态完备。集成MyBatis有mybatis-spring-boot-starter,操作MySQL有mysql-connector-java,做鉴权有spring-boot-starter-security或者自己写拦截器。权限校验、参数校验、异常处理这些常见需求全都有现成的组件可以接,开发效率提升非常明显。

第三,测试和排查方便。SpringBoot的自动配置在启动日志里会打印当前生效的配置项,出问题时用spring-boot-starter-actuator可以看健康状态、接口映射列表。对于"这套系统要交给别人维护"的场景,这个特性非常加分。

2.2 Vue:让HR和管理层愿意用起来的关键

前端选Vue,同样是在国内中小型管理系统里最成熟的选择。我见过太多后端工程师硬写jQuery页面,功能实现了,但界面丑、操作卡顿,HR用两天就抱怨"还不如用Excel"。管理系统的成败,很多时候不在功能而在体验。

Vue的核心优势是数据驱动视图。比如员工列表页,搜索条件变了、页数切换了、某行数据被修改了,你只需要更新对应的data数据,页面自动重新渲染,完全不需要手动操作DOM。这对表单密集、列表密集的管理系统是刚需。再配合ElementUI组件库,表格、表单、弹窗、分页、日期选择器这些后台管理场景的高频组件开箱即用,开发速度非常快。

很多人纠结一个问题:用Vue2还是Vue3?这套系统源码用的是Vue2(配合ElementUI),原因很现实——Vue2 + ElementUI + 各种第三方库的搭配经过大量项目验证,资料多、坑少,对刚接触前后端分离开发的新手更友好。你在网上搜"vue管理系统模板",绝大多数能直接跑通的都是Vue2版本。如果你愿意花时间踩坑,Vue3 + Element Plus + Vite当然更好,但对于这套系统的定位——完整可运行、方便二次开发——Vue2是最稳的选择。

2.3 MyBatis + MySQL:轻量、可控、贴近业务直觉

持久层用MyBatis,数据库用MySQL,这也是Java中小型项目最经典的搭配。

我经常被问到:为什么不用MyBatis-Plus或者JPA?老实说,MyBatis-Plus的开发效率确实更高,用LambdaQueryWrapper写查询基本不用手写SQL。但这个项目之所以用原生MyBatis,一个重要原因是这个项目具备教学和源码参考价值。当你用原生MyBatis写完整个项目,你会对SQL映射、动态SQL、ResultMap、多表联查有非常扎实的理解。这些知识是Java后端工程师的底层能力,换到MyBatis-Plus只是换了个更省事的工具,但底层逻辑没变。

MySQL的选择则完全是行业共识。中小企业内部系统,数据量撑死在百万行以内,MySQL完全覆盖得住,且开源免费,部署运维资料丰富。MySQL的InnoDB引擎支持事务,人事数据(尤其是薪资、合同这类敏感数据)必须保证写入的原子性。

2.4 这套组合的整体架构图景

整个系统的请求链路是这样的:

浏览器(Vue页面)→ HTTP请求 → Nginx(部署前端静态资源+反向代理) → SpringBoot接口层 → Service业务层 → MyBatis(Mapper接口+XML) → MySQL数据库

开发环境下,前端通过Vue CLI的devServer代理转发请求到后端8080端口,避免跨域问题;生产环境下,Nginx将/api/前缀的请求转发到后端服务。这个架构是当前中小型前后端分离项目的主流标准,学会了这个架构,你以后做任何内部管理系统都能直接套用。

3. 系统功能模块拆解:从一堆数据表看人事系统的真实复杂度

3.1 模块地图:先理清系统到底要管什么

一套合格的中小企业人事系统,至少得覆盖以下六个模块。我把这套系统的模块划分整理成表,你对照着自己的公司需求,看哪些要改、哪些能直接用:

模块核心功能对应数据表业务说明
系统登录/权限JWT认证、用户角色、菜单权限sys_user, sys_role, sys_menu区分管理员、HR、普通员工三种角色
员工管理员工信息CRUD、条件搜索、状态拉黑employee系统的核心主数据,其他模块都引用它
部门与岗位部门树形结构、岗位职位管理department, position部门上下级关系,岗位挂在部门下
考勤管理打卡记录、请假申请、加班调休attendance, leave, overtime和员工表、审批流关联最为频繁
薪资管理基本工资、奖金、扣款、实发计算salary和考勤联动,月底批量生成
公告管理公告发布、查看列表、置顶删除notice全员可见,消息触达用

这六个模块拆完,你再看"会不会很复杂"这个问题就会发现:其实每个模块单独拿出来都不复杂,复杂的是模块之间的数据如何关联,权限如何控制,以及每个功能的边界应该划在哪里。

3.2 数据库设计的核心:员工表就是整个系统的“心脏”

一个系统的数据库设计好不好,看主表就能判断。员工表employee的字段设计,直接决定了后续考勤、薪资、异动这些功能能否顺畅实现。下面是我在这套系统里使用的employee表核心字段设计(为了排版做了精简):

CREATE TABLE `employee` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工ID', `empno` varchar(20) NOT NULL COMMENT '工号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint(1) DEFAULT NULL COMMENT '性别 1男 0女', `birthday` date DEFAULT NULL COMMENT '出生日期', `idcard` varchar(18) DEFAULT NULL COMMENT '身份证号', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `dept_id` int(11) DEFAULT NULL COMMENT '所属部门ID', `position_id` int(11) DEFAULT NULL COMMENT '岗位ID', `entry_date` date DEFAULT NULL COMMENT '入职日期', `education` varchar(20) DEFAULT NULL COMMENT '学历', `status` tinyint(1) DEFAULT 1 COMMENT '在职状态 1在职 0离职', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_empno` (`empno`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';

几个设计要点我特别说一下:

工号用unique约束,业务上天然唯一。中小公司里员工的工号通常由HR手动分配,或者按"年份+序号"生成。这里加上唯一索引,是防止重复录入。

身份证号存明文还是加密?这个要分场景。系统如果部署在纯内网,明文存储影响不大;但如果要暴露到公网,建议至少用AES或者BCrypt做加密处理。源码里目前是明文存储,方便演示和二次开发,你落地到生产环境时一定要评估这个风险。

status字段不用delete而是用状态标记。员工离职不是删除数据——历史考勤、历史薪资都要关联这个人。所以用status=0表示离职,列表查询时默认过滤掉离职员工,但统计报表要能查回来。这个设计几乎所有的管理系统都适用。

create_time和update_time一定不要省。后端很多业务排查都需要看数据什么时候被创建、什么时候被修改。MySQL的DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP能自动维护这两个字段,成本为零。

3.3 部门表的经典写法:递归树形结构

部门表department的难点在于层级关系。中小企业组织架构一般是2-3层——公司下面分技术部、市场部、行政部,技术部下面可能再分一组、二组。用数据表表达这种关系,最常见的就是parent_id自关联。

CREATE TABLE `department` ( `id` int(11) NOT NULL AUTO_INCREMENT, `parent_id` int(11) DEFAULT 0 COMMENT '上级部门ID,0表示根节点', `name` varchar(50) NOT NULL COMMENT '部门名称', `leader` varchar(50) DEFAULT NULL COMMENT '负责人', `sort_order` int(11) DEFAULT 0 COMMENT '排序号', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';

查询的时候,前端部门树组件需要"树形数据"。后端返回一个扁平List,前端递归构建Children;也可以后端用递归查询直接返回嵌套结构。我在源码里是后端构建好树形结构返回的,因为部门层级不深,递归查询的性能完全不是问题。

常见的坑是删除部门时没有检查部门下面有没有员工。如果直接删掉一个还有员工关联的部门,员工表的dept_id就变成"悬空引用"了。处理办法是删部门前先count一下员工数,有人的部门不允许删除,提示"该部门下还有员工,请先调整员工部门"。这一条在代码里必须有,实测很多低年级开发者会漏。

3.4 考勤和薪资联动:最容易跑偏的业务逻辑

考勤和薪资是整套系统里业务逻辑最复杂的两个模块,也是很容易被做成"看起来不错但实际没法用"的典型。

先说考勤。中小企业考勤方式通常是:钉钉/美团打卡机 + 月底人工统计。这套系统里,考勤模块提供三个子功能:

  • 打卡记录维护:手动新增、编辑员工某天的打卡时间
  • 请假申请:员工提交请假单(开始日期、结束日期、请假类型、事由),HR审批
  • 加班登记:记录加班时长,用于后续调休或加班费计算

注意,这套系统没有对接硬件打卡机,原因是对接硬件涉及硬件SDK和API适配,不同牌子兼容性差异很大,不适合作为通用demo。你要在实际项目中接入钉钉考勤或指纹机数据,一般是在这个模块基础上扩展数据导入功能——Excel导入打卡记录,或者调用钉钉开放平台的考勤接口同步数据。

再说薪资。薪资计算的核心逻辑是:应发工资 = 基本工资 + 绩效奖金 + 加班费 - 社保扣款 - 考勤扣款。源码里的salary表设计大致是这样的:

CREATE TABLE `salary` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_id` int(11) NOT NULL COMMENT '员工ID', `year_month` varchar(7) NOT NULL COMMENT '工资月份 如2025-06', `base_salary` decimal(10,2) DEFAULT NULL COMMENT '基本工资', `bonus` decimal(10,2) DEFAULT 0.00 COMMENT '绩效/奖金', `overtime_pay` decimal(10,2) DEFAULT 0.00 COMMENT '加班费', `social_insurance` decimal(10,2) DEFAULT 0.00 COMMENT '社保扣款', `attendance_deduct` decimal(10,2) DEFAULT 0.00 COMMENT '考勤扣款', `total_salary` decimal(10,2) DEFAULT NULL COMMENT '实发工资', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_emp_month` (`emp_id`, `year_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='薪资表';

注意uk_emp_month这个联合唯一索引——一个员工一个月只能有一条薪资记录,这从根本上避免了重复发工资的数据问题。

薪资是否要和考勤自动联动?比如"请假一天扣200""迟到三次扣50"。这个需求很常见,但我不建议在通用系统里做死逻辑。因为每家公司的考勤扣款规则差异极大,有的公司按小时扣,有的按次扣,还有的"每月允许迟到三次不计较"。源码里的做法是:考勤模块统计异常情况,薪资模块手动录入扣款金额。想做自动联动的人,可以在现有表结构上扩展一个"扣款规则配置表",但这属于二次开发范畴了。

3.5 权限模型:三个角色,够用就好

权限设计在管理系统里是个无底洞——RBAC、菜单权限、按钮权限、数据权限,复杂起来没完。针对中小企业人事系统,我建议把权限做简单:三种角色,角色-菜单绑定。

  • admin(管理员):拥有全部菜单权限,可以管理所有模块,包括角色分配和用户创建
  • hr(HR专员):拥有员工管理、考勤管理、薪资管理、部门管理权限,公告可以发布
  • employee(普通员工):只能看到公告、个人信息、自己的考勤记录和请假申请

前后端权限控制是双层配合的:

后端用SpringMVC拦截器拦截请求,校验JWT令牌中携带的用户角色,然后判断请求的URL是否在该角色允许的权限列表里,不允许直接返回403。前端根据登录后返回的菜单列表动态渲染侧边栏,没有权限的菜单根本不会显示。注意前端控制只是体验优化,后端校验才是安全底线——不过"前端隐藏菜单"经常被人当成"安全控制"用,这是错误的,任何接口只要调通了就能拿到数据。

权限明细存在sys_role_menu关联表里,一个角色对应多个菜单ID。如果你改了菜单表里的新增菜单,只需要在sys_role_menu里给对应角色插入一条记录即可,不需要改代码。

4. 核心功能模块的代码落地与设计取舍

4.1 JWT登录认证:为什么不建议用Session

登录认证这块,我直接选了JWT(JSON Web Token)。原理很简单:

用户登录成功后,后端根据用户信息生成一个JWT令牌(本质上是一段经过签名加密的JSON数据),返回给前端。前端每次请求在Header里带上Authorization: Bearer ,后端拦截器解析token,能解析出来就放行,解析失败就返回401。

相比Session方案,JWT有两张王牌,在使用时就会体会到:

第一,无状态,天然适合前后端分离。Session方案要求服务端保存会话信息,如果做了负载均衡(两台服务器轮询),Session需要单独存到Redis里共享。JWT本身携带用户信息,任何一台服务器都能独立校验,不需要共享存储。

第二,移动端友好。同一个后端接口,Web端和App端都可以通过JWT接入,不需要做额外的会话适配。

但JWT也有它自己的坑,最大的就是过期时间设置。过期时间太短——比如30分钟,用户操作一会儿就被强制下线,体验很差;太长——比如7天,token泄露了风险也大。源码里的方案是设置24小时过期,加上前端在收到401时自动跳转登录页,实际体验下来是能接受的。更完善的方案是RefreshToken机制,但这就属于进阶优化了,源码里没做,原因是不想过度设计。

登录接口的核心逻辑如下:

@RestController @RequestMapping("/api/auth") public class AuthController { @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 根据用户名查询用户 SysUser user = sysUserMapper.selectByUsername(loginDTO.getUsername()); // 2. 校验密码(BCrypt加密比对) if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 3. 生成JWT,带上用户ID和角色 String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); // 4. 查询当前用户的菜单权限列表(用于前端渲染侧边栏) List<MenuVO> menus = menuMapper.selectMenusByRole(user.getRole()); return Result.success(new LoginVO(token, menus, user.getName())); } }

很多新手容易忽略第4步——登录接口返回菜单列表。这一步省了,前端就得再单独调一次权限接口,而且首次进入系统时菜单渲染会白屏闪烁。登录时一次性把菜单带回去,前端拿到token和menus直接初始化状态,体验会好很多。

4.2 部门树查询:递归在业务代码里的标准姿势

部门树是管理系统的常见需求,代码写法很固定。我给出一段从后端构建树形结构的核心逻辑:

public List<DepartmentVO> buildDeptTree(List<Department> allDepts) { // 转成VO(扩展children字段) List<DepartmentVO> voList = allDepts.stream().map(DepartmentVO::new).collect(Collectors.toList()); List<DepartmentVO> tree = new ArrayList<>(); // 先找出所有根节点,然后递归挂载子节点 for (DepartmentVO dept : voList) { if (dept.getParentId() == 0) { tree.add(dept); } } for (DepartmentVO root : tree) { findChildren(root, voList); } return tree; } private void findChildren(DepartmentVO parent, List<DepartmentVO> allDepts) { for (DepartmentVO dept : allDepts) { if (parent.getId().equals(dept.getParentId())) { parent.getChildren().add(dept); findChildren(dept, allDepts); // 递归往下找 } } }

这段代码的核心思路是"先建森林,再挂树枝"。第一次循环找根部门(parent_id = 0),第二次递归给每个根部门找儿子、给儿子找孙子。数据量在几百个部门以内,这种写法完全够用,不需要上什么复杂的算法。

前端Vue这边,ElementUI的el-tree控件直接支持树形数据渲染,把后端返回的JSON扔进去,写上:props="{ label: 'name', children: 'children' }"就能展示。前后端字段名必须保持严格一致(children是后端定义好的),这个约定要提前想清楚,不然后端返回了children前端却不认识,白白浪费半小时。

4.3 员工信息管理:CRUD之外的三个细节

员工管理模块的增删改查本身很简单,但有几个细节处理得好不好,直接拉开"能用"和"好用"的差距。

搜索条件的分页查询要支持组合条件。员工列表页通常有搜索栏——姓名、部门、状态、入职时间范围。我的做法是在Mapper XML里写动态SQL:

<select id="pageQuery" resultType="com.example.entity.Employee"> SELECT * FROM employee <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null"> AND dept_id = #{deptId} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select>

注意<where>标签的用法——它自动处理条件前缀AND,当所有条件都不满足时不带WHERE子句。MyBatis动态SQL是原生的核心技能,我强烈建议手写,比依赖工具自动生成的代码可靠得多,也更容易调试。

出生日期和身份证号码的关联校验。员工录入表单里,填写身份证号后自动提取出生日期和性别。这属于锦上添花的功能,但HR真实使用时会觉得很贴心。实现就是在前端通过JS解析身份证号(第7-14位是出生日期,倒数第二位奇偶决定性别),自动填到日期字段和性别字段里,禁用手动修改。

离职状态的管理。员工离职不是直接删除,而是修改status为0。薪资表里如果还在计算这个员工,就应在快照的时候就判断状态。一个简便方案:薪资生成页面只加载在职员工列表,离职的人不会再被选中。这个逻辑简单直接,不容易出现"给离职员工照发工资"的bug。

4.4 薪资管理模块:用“按月快照”思路规避数据变化风险

薪资管理模块有一个很容易被忽视但非常重要的设计——薪资数据的快照特性。

什么叫快照?就是你发的是"入职时定的工资",还是"这个月实际工资"。员工升职加薪后,基本工资变了,但你不可能把上个月的工资记录也改掉。薪资表里的base_salary是生成工资记录那一刻的金额,不是实时从员工表反查的。这种"开户容易,销毁困难"的数据,处理方式必须业务明确:工资单生成后不允许修改,只能在当月发工资之前通过"撤销重算"或者"手动订正"来处理。

源码里这一点也比较直白——薪资表没有关联"薪资标准表",就是月末录入的时候直接填金额。这样做的好处是简单灵活,符合中小企业实际(很多公司的薪资本来就是见面的),坏处是没有"调薪记录"的完整链路。如果你需要做调薪管理,可以给员工表加一个current_salary字段,再新增一张salary_adjust_record表记录每一次调薪的金额、时间、原因,形成完整审计链。

4.5 统计报表与Excel导出:后端工程师的隐藏加分项

最后提一下报表模块。管理系统里,列表做得花哨不如能导出一张格式干净的Excel。HR月底要给老板发"各部门人数统计""本月入离职情况",如果系统不能导出Excel,她就得截图或者手动抄表,体验大打折扣。

这套系统里,Excel导出的实现方式是使用Apache POI,在后台把查询结果写入Workbook,然后通过HttpServletResponse以流的形式输出。核心逻辑很简单:

// 构造Excel对象 XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("员工信息"); XSSFRow header = sheet.createRow(0); header.createCell(0).setCellValue("工号"); header.createCell(1).setCellValue("姓名"); // ...填充其他列 // 遍历数据,逐行写入 for (int i = 0; i < list.size(); i++) { XSSFRow row = sheet.createRow(i + 1); row.createCell(0).setCellValue(list.get(i).getEmpno()); // ... } // 设置响应头,浏览器自动下载 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=employee.xlsx"); workbook.write(response.getOutputStream());

POI虽然引入后项目会多几MB依赖,但导Excel是管理系统的刚需,这依赖加得值。而且注意Excel文件名最好加上日期,如员工信息_20250620.xlsx,否则HR每天导出一份文件,文件名全叫employee,很快磁盘就会堆满一堆同名文件。

5. 从源码到上线:环境准备与部署避坑实录

5.1 本地开发环境搭建

拿到源码后,很多人第一步就卡在环境上。我把需要准备的东西列一个清单,按顺序装完直接能跑:

  • JDK 1.8+(推荐8或11,不要用17除非你改过pom里的依赖版本)
  • Maven 3.6+
  • MySQL 5.7+(推荐8.0,兼容性更好)
  • Node.js 14+(Vue2的Vue CLI需要)
  • IDE:后端用IDEA,前端用VSCode或者IDEA都行

数据库初始化的步骤是这样的:

  1. 用Navicat或命令行创建数据库:CREATE DATABASE hr_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  2. 导入源码中sql目录下的hr_system.sql文件,里面有建表语句和基础数据,包括默认的管理员账号。
  3. 修改后端application.yml里的数据库连接配置,把用户名密码改成你自己的:
spring: datasource: url: jdbc:mysql://localhost:3306/hr_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: your_password

serverTimezone=Asia/Shanghai这个参数在MySQL8.0里尤其重要,不加的话插入时间字段会报"无法识别服务器时区"的错误,这是新手最容易踩的坑。

前后端联调之前,后端先启动,前端的API请求地址要配置好。Vue项目在vue.config.js里配置代理转发:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

这样前端所有请求路径写/api/auth/login,请求就会自动转发到后端8080端口,完美躲避跨域。

5.2 生产环境部署:Nginx与jar包并肩作战

项目完成开发后,生产部署流程我推荐这种方式:前端打包成静态文件交给Nginx服务,后端打成jar包交给Java进程运行,Nginx把/api前缀的请求反向代理到8080端口。

前端打包命令是npm run build,产物在dist目录下。把dist目录里的文件拷到服务器Nginx的html目录,后端用mvn package -DskipTests打出jar包,扔到服务器上nohup java -jar hr-system.jar > log.txt 2>&1 &运行。

Nginx的关键配置如下:

server { listen 80; server_name your_domain_or_ip; # 前端静态资源 location / { root /usr/share/nginx/html/hr_system; index index.html; try_files $uri $uri/ /index.html; # 解决Vue路由刷新404问题 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

try_files $uri $uri/ /index.html;这行是Vue Router的History模式标配——否则用户在员工管理页按F5刷新,Nginx找不到对应的静态文件,会返回404。这也是一个高频报错点,我见过不少同学部署后"页面只能从首页进入"就是少了这行。

5.3 源码使用与二次开发时要注意的几个坑

把项目通读一遍后,我发现这类源码最容易出问题的地方集中在三个部分,如果你打算基于这套源码二次开发,提前避开:

登录拦截器容易漏配白名单。如果不放行登录接口和静态资源,会导致所有人无法登录、前端拿不到验证码图片。源码里在WebMvcConfigurer中配置了interceptor,并且用excludePathPatterns排除了/login、/captcha等路径。二次开发时每加一个公开接口,记得同步更新白名单。

跨域配置只写一半。有些同学生产环境不用Nginx反代,直接让前端请求后端完整地址,然后又没配CORS,就会导致"明明接口在浏览器里能访问,但Ajax请求就是报跨域"。这个问题的根源是浏览器的同源策略,跟网络通不通没关系。处理方式要么是开发环境用代理,要么后端加cors过滤器。两者选其一,不要混着来,混着配置反而会出怪问题。

数据库SQL文件里的初始密码要记得改。源码自带的管理员密码通常是123456或者admin这种弱口令,第一次登录后一定要到用户管理里改掉。我们做过安全测试,默认口令是这类内网系统被攻破的头号原因。

6. 踩壳实录:我在复现和调试这套系统时遇到的三个问题

写这篇文章之前,我把这套系统完整地从零搭建和调试了一遍,中间遇到三个很有代表性的问题,拿出来给大家当案例参考,你们在部署时如果碰到类似问题,可以少走弯路。

6.1 Vue项目npm install报错,node-sass无法下载

现象:npm install执行后,node-sass相关的安装包下载超时或失败,终端全是红色的error日志。

排查过程:先看报错信息,会发现下载路径指向GitHub发布的二进制包。国内环境访问GitHub不稳定,这是最常见的原因。我按三步排查:先确认node版本是否兼容(node-sass对node版本很敏感,node 16以下才兼容node-sass 4.x,node 17以上基本必挂);再确认是网络问题还是依赖版本问题;最后尝试切换到cnpm镜像或yarn安装。

解决:最稳妥的方式是改用sass实现——安装sass和sass-loader,然后在vue.config.js里设置css: { loaderOptions: { sass: { implementation: require('sass') } } }。或者干脆用cnpm镜像走阿里云的源,下载速度会快很多。

6.2 MyBatis XML里的SQL在启动时报“Invalid bound statement”

现象:后端启动成功后,调用某个Mapper接口的方法立即报错,错误信息提到“Invalid bound statement (not found)”。

排查过程:这类问题的根源是Mapper接口和Mapper XML没有建立映射关系。优先级从高到低检查:确认Mapper接口的全限定名和XML文件的namespace一致;确认XML文件放在resources/mapper目录下,并且Maven打包时没有被过滤掉;确认application.yml里配置了mybatis.mapper-locations: classpath:mapper/*.xml;检查mapper接口的方法名和XML里的id是否完全一致(大小写敏感)。

解决:我最后发现是targe目录下编译产物里根本没有XML文件,因为Maven默认只把resources目录下的资源打进去,如果XML放在java目录下,就会漏掉。把XML移到resources/mapper目录,重新编译,问题解决。这个坑在真实项目里出现频率极高,新手和老手都会踩,只是新手不会排查而已。

6.3 薪资表的数据精度问题:金额用了double还是decimal

现象:薪资计算后,显示的总金额出现类似459.000000001这样的尾差。

排查过程:这就涉及一个关键选择——金额字段用哪种Java类型。源码里数据库用decimal(10,2),但Java实体类里如果用Double接收,经过多次运算就可能出现浮点精度误差。我检查后发现,部分接口返回时BigDecimal被序列化成字符串,前端再解析成Number,又经历了一次精度损耗。

解决:金额相关的实体字段全部改用BigDecimal,避免double的二进制浮点精度问题。前端展示时统一用vue过滤器格式化为两位小数。这个教训适用于所有和钱打交道的系统,初期数据类型定了什么,后期返工的代价是巨大的。

7. 最后的实操建议:这套系统的下一步还能怎么扩展

如果你准备把这套系统用在真实的公司环境里,或者打算以它为基础做毕业设计、项目演示,我给你几条实操层面的建议,都是这套系统实际投入应用时容易遇到的延伸需求:

第一,员工自助功能一定要补上。现在普通员工登录后只能看公告,如果能把"申请请假""查看工资条""维护自己的紧急联系人"这些自助功能加进去,这套系统才算真正形成了闭环。做这些功能的技术难度不大,无非是给employee角色增加对应的菜单权限和数据权限——员工只能操作自己的数据。

第二,考勤打卡对接。如果公司有钉钉或企业微信,不要自己去写硬件对接代码,直接找开放平台的官方接口,同步打卡记录到考勤表即可。这类对接的核心是处理好时间格式和数据幂等——同一批打卡记录重复同步不能产生重复数据。

第三,数据备份策略。人事系统里的员工合同、薪资明细、身份证号都属于敏感数据,建议数据库每日全量备份,保留至少30天。备份用mysqldump一行命令就能加入crontab定时任务,成本很低但价值极大。

第四,报表可视化。当前系统的统计功能可能只停留在简单表格层面。你在掌握了Vue和ECharts之后,顺手加几个图表(部门人数分布饼图、近半年的入离职趋势折线图、薪资成本漏斗图),这个系统在工作汇报中的价值会立刻上一个台阶。

最后说句实在话:人事管理系统的技术含量并不在于高深算法,而在于对业务的理解和对边界情况的处理。把员工信息、考勤、薪资这三滩数据管得清清楚楚,让HR告别Excel地狱,这套系统的价值就充分体现了。顺着这个思路走下去,你会发现所谓"管理系统开发",核心就是"把人在某个业务领域里的工作习惯和规则,翻译成系统逻辑"的一门手艺。这套源码里已经给了你一个相对标准的翻译范例,剩下的事情,就是用你的业务理解去充实它了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询