一个内部管理系统的选型从来不是越高级越好而是在团队接受度、维护成本和业务复杂度之间找到一个平衡点。之前有位做生产的客户内部人事数据一直用几份Excel来回传考勤统计靠手工加总每个月末就是人事部门的“灾难日”。后来我帮他们整理了一套基于Spring Boot Vue的人事管理系统前后端分离带完整的MySQL数据库脚本和部署文档。下面把整个项目的选型思路、表结构设计、后端接口、前端页面以及联调部署的坑都摊开聊一聊希望对你搭建类似系统有实际参考价值。1. 这套系统的定位一个能直接跑起来的人事管理原型做管理系统最怕的就是“需求无限蔓延”。人事系统的核心价值归根结底是三类员工信息能存能查、考勤薪资能算能导出、权限角色能分能管。围绕这三个核心诉求我把项目收敛成六大模块不做大而全但每个模块都能独立使用。1.1 六大功能模块与实际场景模块划分直接影响数据库设计和接口数量这里列出我最终确定的清单员工档案管理员工基本信息的新增、编辑、查询、离职操作支持照片上传和附件归档。部门与岗位管理部门以树形结构维护岗位与部门关联员工挂在具体部门下。考勤管理记录每日打卡或者补卡数据支持按月汇总出勤天数。薪资管理根据基础工资、岗位工资、考勤扣款和补贴生成月度薪资单。系统用户与权限登录用户、角色、菜单权限三层模型不同角色看到不同菜单和操作按钮。数据统计与导出部门人数分布、员工状态统计以及员工花名册Excel导出。上面这套功能覆盖了绝大多数中小型团队人事管理的日常场景。而对于一些诸如招聘流程、培训记录、绩效考核之类的延伸功能我建议在原型跑通之后再迭代。如果你第一次就尝试把所有模块塞进去大概率会在数据库设计阶段就陷入僵局。1.2 这套源码适合谁参考从实际受众来看这套系统有三类人会比较适用。第一类是刚接触前后端分离项目的学生朋友用它当课程设计或毕业设计的底子结构清晰文档齐全。第二类是需要快速搭建内部管理工具的小微企业技术人员给非技术团队做试用版选型参考。第三类是自己接外包的小团队拿这套系统改改就能交付给客户省去从零建表的成本。总的来说这套系统的定位是一个“高完成度的半成品”数据库脚本给你列好了接口文档给你写清楚了前端页面能直接登录操作剩下的业务细节完全可以按自己的需求再加。2. 技术栈看着很常见为什么最终选了Spring Boot Vue关于技术选型很多文章喜欢罗列一堆理由但落到实际项目里核心其实就几个问题这个技术大家熟不熟、部署维护难不难、出了问题有没有人能接盘。2.1 后端选型对比我在做这个项目时后端候选方案主要有三个Spring Boot、Node.js Express、还有传统PHP。三者各有优势但结合人事管理系统的特点我倾向Spring Boot。Spring Boot的生态非常成熟尤其是Spring Security、MyBatis Plus这些组件用起来省掉大量重复代码。而且Java后端在中小型系统里最不缺的就是维护者哪怕你交接给另一个团队也能很快接手。相比之下Node.js在开发速度上有优势但工程化管理和权限体系要自己拼装PHP上手门槛低但前后端分离模式下的接口开发和鉴权实现反而更折腾。在Spring Boot版本选择上我建议用Spring Boot 2.7.x配合JDK 1.8或11。不是3.x不好而是2.x在兼容性上更稳网上资料也多尤其是对接各种中间件时踩坑成本低。如果你的环境允许使用较新版本那Spring Boot 3.x配JDK 17是趋势但一定要提前确认所有依赖都适配了否则很容易卡在版本陷阱里。对比维度Spring BootNode.jsPHP团队上手难度中等中等低生态成熟度高中高高前后端分离开发体验好好一般长期维护成本低中中毕业设计/面试加分高中低2.2 前端技术栈的取舍前端的选型相对明确Vue加Element UI组件库是当前国内做后台管理系统的经典组合。我这个项目里用的Vue版本是2.6配Element UI 2.15。有人会问都202X年了怎么不用Vue3加Element Plus我的理由很简单开源生态里Vue2的存量项目和使用人群依旧庞大各种踩坑方案都能搜到。对于毕业生或者外包项目技术新颖不一定等于好交付稳定可查才是第一位的。当然如果你完全从零开始没有历史包袱Vue3加Element Plus也完全可行。这套系统的接口设计和页面逻辑是前后端分离通用的换成Vue3后只需要重写组件引入方式接口层不用动。2.3 中间件与构建工具的搭配数据库我用的是MySQL 5.7主要考虑到客户服务器上已经装了这个版本迁移成本为零。如果你手上是MySQL 8.0脚本一样能跑只需要留意驱动版本和时区配置差异。ORM框架选了MyBatis Plus它的分页插件和代码生成器能让开发效率提升一半尤其在后端接口多、表单多的管理系统里这种半自动ORM比原生MyBatis舒服很多。构建工具方面后端用Maven前端用npm加vue-cli都是最主流的方案没有任何学习门槛。整套技术栈总结成一句话不是最新最炫的但一定是最容易找到资料、最容易让人接手继续改的。3. 数据库设计的关键细节部门树、员工状态、逻辑删除说实话人事系统真正的核心不在前端页面长什么样而是在数据库设计。后面接口写得顺不顺畅报表统计能不能做出来全看这一层。3.1 核心表结构一览整个数据库我拆成近二十张表但核心的也就下面几张先看表名和作用。sys_user系统登录账号字段包括id、username、password、nickname、avatar、status、dept_id。sys_role角色表定义管理员、人事专员、普通员工等角色。sys_user_role用户和角色的关联表用户与角色是多对多关系。sys_menu菜单权限表前端动态菜单和后端接口权限都依赖这张表。dept部门表用parent_id实现层级关系比如公司生产部车间一组。position岗位表和部门是弱关联一个部门可以有多个岗位。employee员工档案主表字段最多的一张表下面会重点展开。attendance考勤表记录员工每天的上、下班时间。salary薪资表按月存储每个员工的薪资明细。sys_user和employee是分开的。很多初学者容易混其实逻辑很简单admin账号对应系统登录身份employee记录的是员工真实信息两者通过user_id字段关联。一个员工未必有登录账号一个登录账号也不一定对应在职员工。3.2 员工表字段设计的讲究employee表是整个系统的数据枢纽字段看似多但每一个都有明确用途。字段名类型说明idbigint主键emp_novarchar员工编号唯一索引namevarchar姓名gendertinyint性别id_cardvarchar身份证号phonevarchar手机号dept_idbigint所属部门position_idbigint岗位hire_datedate入职日期statustinyint员工状态1在职 2试用 3离职avatarvarchar照片地址deletedtinyint逻辑删除标记create_timedatetime创建时间update_timedatetime更新时间这里有几个设计细节特别注意。第一emp_no必须加唯一索引。员工编号是业务上识别员工的关键字段一旦重复后面考勤、薪资关联数据全乱。并且这个编号最好在新增员工时由系统自动生成比如“EMP”加年月日加三位流水号而不是让用户手输这样能极大减少脏数据。第二status只做单字段状态管理。有些系统喜欢用“在职/离职”两个表来区分查询时就必须JOIN两张表非常别扭。用一个status字段加一个离职时间remark既能满足列表筛选也能在后续做统计分析时直接分组。第三deleted逻辑删除是必选项。人事系统里员工档案是非常敏感的数据如果误删再也找不回来就麻烦了。所以表里统一加deleted字段删除操作只是把deleted改成1查询时自动过滤。这样既保证数据不丢又不影响列表查询的性能。3.3 部门树的设计与查询思路部门表用自关联的方式维护层级结构核心字段就是parent_id。建树的方式有两种。一种是在SQL里递归查询MySQL 8.0以后支持递归公共表表达式写起来很简洁MySQL 5.7则要写自定义函数或者循环查。另一种是把所有部门一次性查出来在Java代码里根据parent_id构建成嵌套对象前端直接渲染树组件。我这里用的是第二种因为部门数量一般不会超过几百个一次查完内存构建更简单数据库压力也小。构建树的核心逻辑可以这样理解先查全部部门列表再遍历一遍把每个节点的子节点列表填充进去。这就是最常见的“遍历建树”操作代码量不大但能让你避开反复查库的性能陷阱。4. 后端实现认证、统一响应、员工档案和Excel导入导出后端模块我按功能切分成三块来写基础架构、权限认证、业务接口。基础架构决定了你后续写接口的舒服程度权限认证决定了系统的安全性而业务接口则是用户每天都要使用的核心功能。4.1 统一响应与全局异常后端的地基好的后端项目首先需要一个统一的JSON响应结构。我内部定义的格式是{ code: 200, message: 操作成功, data: {} }所有接口都返回这个格式前端通过code来判断业务是否成功通过message来提示用户。这个统一结构的好处越到后期越明显。比如登录失败返回401、权限不足返回403、业务校验不通过返回500前端只需要写一个拦截器就能统一处理所有异常情况不需要每个接口单独判断。全局异常处理同样重要。使用Spring的RestControllerAdvice注解统一兜底可以避免出现异常时返回Spring默认的错误页面。我这里定义了针对参数校验异常、业务异常、未知异常的三层处理逻辑参数校验异常提示用户具体字段错了请修改业务异常提示操作请重试或检查状态未知异常才记录完整错误日志。4.2 JWT认证与权限控制的实现思路登录鉴权用的是JWT加拦截器方案整体流程是这样的。用户首次登录时后端校验用户名密码通过后生成一个token返回给前端前端存到localStorage里。之后每次请求都在Header里带上Authorization字段后端拦截器解析token校验通过则放行。具体落地我分了两个层次。第一层是登录拦截器校验token是否存在且有效第二层是基于菜单权限的接口权限控制当前用户拥有的菜单编号如果不在允许列表内接口直接返回403。JWT实现本身并不复杂关键是要注意token过期时间和用户状态变更的问题。我设置的token有效期是24小时前端在收到401响应时自动跳转登录页。另外一个容易被忽略的问题是当用户被管理员禁用后如果token还没过期他依然能访问系统。所以我在拦截器里除了验证token还会每次查一下用户状态发现被禁用就立即失效。4.3 员工档案接口的具体设计员工管理接口是整个系统最关键的CRUD模块我把它分成几个接口来设计。GET /api/employee/page分页查询员工列表支持姓名、部门、状态、入职日期区间筛选。GET /api/employee/{id}查询员工详情。POST /api/employee新增员工。PUT /api/employee修改员工信息。DELETE /api/employee/{id}逻辑删除员工。GET /api/employee/export导出员工花名册Excel。分页查询用的是MyBatis Plus的分页插件。前端传入current和size两个参数后端返回total和records两个字段。前端表格组件只需要把这两个字段对应上即可实现分页。查询条件的处理上我建议用条件构造器而不是自己拼SQL这样代码整洁也避免SQL注入风险。新增和修改员工时后端必须做字段校验。比如手机号是否符合格式、入职日期不能早于出生日期、员工编号是否重复这些校验规则写在服务层而不是前端。后端校验的意义在于哪怕有人绕过前端直接调接口数据也一样是干净的。4.4 使用POI实现员工花名册导出导出Excel在做管理系统的项目里是非常加分的功能。员工花名册导出用的是Apache POI库流程大概是创建Workbook创建Sheet设置表头逐行填充员工数据最后通过HttpServletResponse把文件流写回前端。导出的细节处理也很有讲究。表头建议先加粗并设置背景色列宽按内容长度自适应。身份证号和手机号必须设置成文本格式否则在Excel里会因为超过一定长度被科学计数法显示。一个很典型的坑就是明明是15位身份证号导出后显示为1.1101E14所以要用CellStyle设置数据格式为文本。注意Excel导出接口一旦数据量大就不能同步导出否则接口超时是必然的。超过两万条数据建议加一层异步导出机制前端发起导出请求后端生成文件后存入临时目录然后返回文件下载链接。这套系统目前的数据量还是同步导出但留了异步接口的扩展位。4.5 部门树与统计接口的实现部门树接口返回的是嵌套结构Java里用List类型的DeptVODeptVO再包含一个List类型的下children。这样前端拿到后直接绑定Element UI的树组件就能完美展示。统计接口则用SQL的GROUP BY加COUNT查出每个部门的人数前端用饼图展示。这两个接口写的顺不顺全看数据库设计。部门表有parent_id员工表有dept_id统计查询只需要一条SQL就能完成不需要在业务代码里做二次聚合。5. Vue前端搭建动态路由、权限按钮和表单校验前端部分我也可以唠唠实际经验。很多人觉得后台管理页面就是表格加表单没什么技术含量但真做起来路由守卫、状态管理、组件封装这些都直接影响使用体验。5.1 前端工程结构与依赖安装前端项目基于vue-cli创建目录结构大概如下。hrms-web/ ├── src/ │ ├── api/ # 接口请求模块 │ ├── assets/ # 静态资源 │ ├── components/ # 通用组件 │ ├── layout/ # 整体布局侧边栏、顶栏、主内容区 │ ├── router/ # 路由配置 │ ├── store/ # Vuex状态管理 │ ├── utils/ # 工具方法如request.js │ ├── views/ # 页面视图 │ ├── App.vue │ └── main.js依赖方面重点装了四个库axios、vue-router、vuex、element-ui。element-ui按需引入可以缩小打包体积但为了方便后期维护我直接全量引入系统部署在内网体积对访问速度影响不大。5.2 路由守卫没有token就跳登录路由守卫是前端安全的第一道门。我在全局前置守卫里做了三件事检查本地有没有token没有token的强制跳转登录页有token但访问的是登录页则跳转首页进入每个页面时再根据菜单权限判断用户是否被允许访问不允许就跳转403页面。这里有一个容易踩的坑动态路由和刷新页面时的状态丢失。如果你的菜单权限是根据用户角色动态生成的那么刷新页面时Vuex里的菜单状态会清空路由守卫会认为用户没有权限而跳转到403。我当时的解决方法是路由守卫里先异步请求后端获取当前用户的权限菜单再动态添加路由。也就是说刷新页面后要等菜单权限加载完成再放行。5.3 基于角色的按钮权限控制后端接口肯定要做权限控制但前端也要配合控制用户能看到哪些操作按钮。我的方案是基于Vue自定义指令实现一个v-permission指令的值是一个权限标识如果当前用户没有这个标识对应按钮直接移除。举个例子。el-button v-permissionemployee:add新增员工/el-button这个指令的实现核心是查询当前用户权限列表是否包含employee:add不包含就把DOM删掉。这样普通员工登录后根本看不到新增、删除按钮从交互层面减少误操作。菜单权限的数据源是sys_menu表。后端提供一个GET /api/auth/menus接口根据当前用户角色返回菜单列表前端渲染成侧边栏。这样不同的角色登录系统看到的菜单本身就不同。5.4 表格、搜索和表单弹窗的通用模式员工管理页面是标准的“搜索区加表格区加弹窗表单”结构。我在组件设计上做了个封装把所有弹窗表单统一成一个通用的DialogForm组件内部处理打开关闭、表单校验、提交逻辑。每个业务页面只需要传一个表单配置文件比如有哪些字段、字段类型是输入框还是下拉框组件就自动渲染出来表单。这种做法一开始有点费工夫但后面新增业务页面时几乎就是复制粘贴改配置效率翻倍。不过要提醒一下过度封装也可能导致代码难维护特别是项目只有两三个人开发时简单直接的页面反而更舒服。所以封装程度要视团队规模而定技术不是目的好用才是。5.5 Axios请求封装与常见错误处理request.js是整个前端和后台交互的枢纽。我封装的逻辑包括给所有请求自动加token请求头响应拦截器里判断code字段如果返回401就清除本地登录状态并跳转登录页403则统一提示没有权限其他业务错误直接弹出message提示。这里有一个处理细节值得提一下后端返回的文件流和普通JSON需要使用不同的解析方式。例如导出接口返回的是二进制数据如果统一走JSON解析就会乱码。解决办法是在axios封装里加一个responseType参数导出时手动设置成blob然后通过实例化FileReader或URL.createObjectURL来触发下载。6. 从联调到部署跨域、Nginx和最容易翻车的几处配置前后端分离的项目本地跑通只是第一步真正让系统交付给客户使用还包括跨域配置、生产环境打包、Nginx反向代理部署这几个环节。这里面任何一个细节出问题前端页面都会白屏或者接口全部报错。6.1 前端跨域与代理配置开发环境下前端在localhost:8080后端在localhost:8081Ajax请求默认是跨域的。后端虽然可以通过配置CrossOrigin注解来解决但生产环境走Nginx时这个配置又可能冲突所以我更推荐前端用vue-cli的proxy来解决开发环境跨域。在vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } };这样前端请求/api/employee/page会被自动转发到后端8081端口浏览器看到的是同源请求不需要后端开启跨域。代价是后端接口需要统一加/api前缀或者通过context-path设置。我这里的做法是后端所有接口统一访问路径前缀为/api生产环境Nginx再把/api转发给后端服务。6.2 Nginx部署与静态资源处理生产环境部署时前端的npm run build会生成dist目录把dist部署到Nginx里配置一个server块root指向dist目录。然后还需要加一条location规则将/api路径代理到后端服务。location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }另外一个最关键的就是前端路由的history模式需要配置try_files否则刷新某个子页面时nginx会报404。正确配置是location / { root /opt/hrms-web/dist; index index.html; try_files $uri $uri/ /index.html; }这句try_files的含义是如果请求的URL对应不上真实文件就统一返回index.html由前端路由接管页面渲染。没有这一行你会遇到一种特别诡异的情况从首页点进去操作一切正常一刷新子页面就404。6.3 数据库连接和时区问题后端application.yml里配置数据库连接时最容易遇到时区和SSL问题。MySQL 8.0以后连接串建议写成jdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiserverTimezone必须显式指定不然MySQL和Java的默认时区可能不同查出来的时间字段会差八个小时尤其是考勤和薪资这种对时间敏感的业务数据错乱会让用户直接抓狂。6.4 常见联调问题排查思路联调阶段我总结了三类高频问题可以当排错清单来用。第一类是登录接口通了但其他接口全部401。优先检查前端有没有把token放进请求头以及后端拦截器解析token的加密秘钥是否一致。第二类是页面能访问但接口报504一般情况下是后端服务没启动成功或者Nginx代理的target地址打到错误端口。第三类是前端能登录但是页面空白查看浏览器控制台排查是不是动态路由加载顺序有问题或者路由守卫把页面全部拦截了。这三类问题覆盖了我实际项目中遇到的大部分场景每次排查都遵循一个原则先前端控制台看请求是否发出再看后端日志看异常堆栈最后看Nginx错误日志验证代理规则。一条链路从上到下排查基本不会漏。7. 源码、数据库脚本和文档拿到的项目里都有什么这部分主要说说交付内容的结构。拿到这套项目的源码包之后你不会面对一个不知所措的文件夹所有东西都按后端、前端、数据库、文档四块组织好。7.1 后端工程结构说明后端是一个标准的Maven工程核心目录如下。backend/ ├── sql/ # 数据库初始化脚本 ├── src/main/java/ │ ├── com.hrms.common/ # 统一响应、异常处理、工具类 │ ├── com.hrms.config/ # 配置类包括拦截器、跨域、文件上传 │ ├── com.hrms.controller/ # 接口控制器 │ ├── com.hrms.entity/ # 实体类 │ ├── com.hrms.mapper/ # MyBatis Plus Mapper │ ├── com.hrms.service/ # 业务逻辑层 │ └── com.hrms.security/ # JWT与权限相关如果要做二次开发大部分情况下你只需要改service层和新增controller新建一张表就对应一个新实体的编写。这种规范的分层结构也方便导师或者团队负责人代码评审一眼就能看清代码属于哪个层次。7.2 数据库脚本的使用方式sql文件夹里有两个文件一个是hrms_schema.sql创建数据库和所有表的脚本另一个是hrms_data.sql初始化的基础数据包含默认管理员账号、菜单权限数据、示例部门岗位和少量员工信息。导入数据库的步骤很简单先创建数据库然后依次执行两个脚本。mysql -u root -p create database hrms default character set utf8mb4;然后使用source命令导入两个SQL文件。这里要重点提醒数据字典菜单权限这部分初始数据必须导入否则admin账号登录后看不到任何菜单前端动态路由加载会直接报错。我遇到过多次这种问题大多都是只导了schema没导data。7.3 快速启动流程后端启动前需要改application.yml里的数据库用户名和密码确保能连上MySQL。然后通过Maven执行打包命令再用java -jar启动。前端则是先npm install再npm run dev启动开发模式浏览器访问localhost:8080。默认管理员账号密码一般是admin和admin123首次登录后建议立即修改密码。文档里面写了一份接口文档每个接口的请求方式、参数说明、返回示例都有标注让接手的人不用去翻源码也能理清接口逻辑。文档中还包含一份部署笔记内容包括Linux环境的JDK和MySQL安装、Nginx配置、防火墙端口开放等基本照着走就能把系统部署到一台干净的服务器上面。8. 个人体会给打算复刻这个项目的人几个实在建议最后聊几个我在开发这个项目过程中体会最深的地方希望能帮后面的人少走弯路。第一不要一开始就追求“全”。我最初设计功能的时候也想把绩效、招聘、培训全部做上但工期和复杂度都不允许。实际落地的项目中员工档案加考勤薪资已经能解决80%的问题把核心链路做稳定远比铺开一堆半成品功能更重要。第二数据库设计一定要花足够的时间推演这个真不能省。我见过太多临时拼凑的系统到后期加一个字段就要改一堆SQL原因就是前期没想清楚关系模型。哪怕后面要改先把核心表的一对多、多对多关系理清楚后面改起来也会轻松很多。第三前端代码尽量抽出公共组件但别过度设计。像员工管理、部门管理这种页面结构高度相似抽一个通用查询表单组件确实能节省很多时间。但是组件之间的参数传递复杂起来后调试成本也会上升。我的建议是先复制粘贴实现两三个页面等确认页面结构稳定后再回过来抽组件这样组件的API设计会更贴合业务而不是凭空拍脑袋。根据我的使用感受这套人事管理系统说到底是一个基础框架加一套可运行的实现。你自己落地的时候可以拿它当骨架需要二次开发的部分很清晰不需要的部分删掉也不影响整体结构。如果你正好要做类似的东西现在就可以动手把数据库初始化好然后从登录接口一路调到员工管理页面相信很快就能跑通整个流程。
阅读完成 · 觉得有帮助?