SpringBoot+Vue人力资源管理系统:从数据库设计到前后端部署全解析
发布时间:2026/9/24 18:35:52 作者:尧图编辑部 阅读量:1,286

做课设或者毕设选题的时候总有人纠结“到底选个什么项目才既不会太简单拿不出手又不会太难做不完”。如果你正在Java全栈这条路上我强烈建议你认真看看SpringBootVue这套组合。这不是什么黑科技但它恰好覆盖了后端接口开发、前端页面交互、数据库设计这几块硬功夫。今天分享的这套人力资源管理系统HRMS是我自己梳理过的、非常适合拿来二次开发或者直接交作业的源码级项目。先说这个项目解决什么问题它把一个企业里人力资源部门日常要干的事——“员工信息管理、部门组织架构、考勤打卡、薪资核算、招聘流程、系统用户权限”全部搬到线上做成一个前后端分离的Web管理平台。对你来说做完它你等于亲手走通了一条“数据库建模→后端接口→前端联调→部署上线”的完整链路这正是企业里真实项目的标准流程。我在给一些学生朋友改这个项目的时候发现大家拿到源码最大的痛点不是看不懂代码而是不知道整个系统是怎么串起来的。所以我这篇分享会从整体设计思路、数据库表结构、后端核心逻辑、前端页面实现到本地部署的完整步骤一步步拆给你看。不管你是纯小白照着做还是想改造成自己的毕设题目这篇文章都能给你一个清晰的地图。需要说明的是文中的代码和方案都基于我实际验证过的一套常见实践具体到你自己的运行环境可能需要做少量适配但思路是完全通用的。1. 整体设计思路为什么这套技术栈值得你选1.1 技术选型的底层逻辑很多同学选技术栈的时候只看“流行”但很少想“为什么”。SpringBootVueMySQL这套组合能够成为课设和毕设的“标准答案”背后是有原因的。先看后端。SpringBoot的核心价值在于“约定大于配置”它把Spring家族繁琐的XML配置全部自动化了。你写一个查询员工的接口只需要一个Controller类加一个Service方法不用像老一代SSH框架那样配一堆Bean。这对课设阶段来说是巨大的效率提升因为你的时间应该花在理解业务逻辑上而不是耗在环境配置里。再看前端。Vue的出现让页面开发从“操作DOM”转向了“操作数据”。你只需要维护一份数据对象页面会自动跟着变。比如员工列表你只要把后端返回的数组赋值给data里的list变量表格就渲染出来了不需要手动去拼接HTML字符串。这种开发体验对新手极度友好而且Vue的生命周期、组件通信这些概念面试的时候还能拿出来说道说道。MySQL就更不用说了开源、免费、资料多遇到任何问题都能在社区找到答案。这套组合还有一层好处——它和目前绝大多数企业的实际技术栈高度重合。你毕业以后去面试Java开发岗大概率会碰到这套东西。做课设的同时顺便把面试基础打了这账怎么算都不亏。1.2 系统功能模块怎么规划人力资源管理系统这个题目看似简单但里面可以做的点非常多。我在规划这套源码的时候把它切成了六大功能块。这里要提醒一下功能不是越多越好而是要形成一个完整的业务闭环。第一个是员工管理。这是整个系统的数据核心所有模块都围绕员工数据展开。包括员工的基本信息增删改查、条件搜索、导入导出可选、员工状态管理在职/离职/试用期。第二个是部门管理。一般做成树形结构因为公司组织架构是分级别的比如总公司下面有技术部、人事部技术部下面又有前端组、后端组。这个模块能体现你对递归数据的处理能力面试官经常问“树形结构怎么存储和查询”。第三个是考勤管理。这块可以作为亮点扩展。基础版本就是记录上下班打卡时间进阶一点可以加上请假审批流程。我会在后面的章节详细讲一套可落地的考勤设计思路。第四个是薪资管理。根据员工基本工资、考勤天数、社保公积金、个税等参数自动计算实发工资。这里涉及金额计算对精度要求高我会告诉你怎么避免出现“0.10.2不等于0.3”这种经典问题。第五个是招聘管理。包括职位发布、简历投递记录、面试状态流转。这个模块能让你把状态机的概念应用到实际业务里比如简历状态从“待筛选”到“面试中”再到“已录用”。第六个是系统管理。这是做管理平台的标配包括用户登录注册、角色权限分配、菜单管理。这个模块决定了你的系统是“一个能跑的小Demo”还是“一个真正可用的系统”。我强烈建议你在毕设里加上JWT认证和RBAC权限控制这绝对是答辩加分项。2. 数据库设计地基打牢后面才不塌2.1 核心表结构设计数据库设计是整个项目里最重要、也最容易被忽视的环节。很多同学上来就写代码写到后面发现表结构不对推倒重来非常痛苦。我设计的这套HRMS数据库总共有8张核心表。这里我挑几张最关键的给大家详细分析。员工表employee是整个系统的核心我设计的核心字段包括id、emp_code员工工号、name、gender、birthday、phone、email、dept_id所属部门、position职位、salary_base基本工资、hire_date入职日期、status在职状态。注意到没有我没有直接在员工表里存部门名称而是存了一个dept_id。这就是数据库设计里的“范式”思想——数据冗余越少越好。部门改名了员工表不需要动部门名称始终从部门表实时关联查出来。部门表department就要体现树形结构了字段设计为id、parent_id上级部门id顶级部门这个值为0、name、leader负责人、phone。这里一定要加parent_id这样就能用一条SQL递归查询出完整的组织架构树。我有一次看到有人设计部门表的时候用“level字段”来区分层级后来部门层级调整时数据全乱了。用parent_id才是正解level可以作为一个冗余字段辅助查询但不能作为主要关联依据。考勤表和薪资表需要特别设计一下。考勤表attendance字段为id、emp_id、att_date日期、clock_in_time上班打卡时间、clock_out_time下班打卡时间、status正常/迟到/早退/缺卡。这里有个细节为什么不直接存“是否迟到”这种计算结果而是存原始打卡时间因为迟到与否是可以动态定义的比如公司规定9点上班但某天弹性上班变成9点半了如果你存的是“是否迟到”这个结果历史数据就全部错了。存原始时间计算规则随时可变这就是“数据与规则分离”的思想。薪资表salary字段为id、emp_id、year、month、base_salary、bonus奖金、deduction扣款、insurance社保、fund公积金、actual_salary实发工资、create_time。实发工资这个字段一定是计算后落库的方便后续对账查询不能每次临时算。2.2 为什么建议用逻辑删除而不是物理删除这是个很经典的设计决策。以员工表为例如果某天操作员手滑删掉了一个员工而这个员工关联着历史考勤、薪资记录那这些关联数据就全悬空了查询全报错。所以我强烈建议在每张业务表上都加一个deleted字段默认0删除时执行的是UPDATE employee SET deleted 1 WHERE id ?而不是DELETE。我在源码里已经把这种逻辑删除的思路统一实现了。这样做最大的好处是数据永远在只是标记为不可见方便数据恢复和审计追踪。代价就是写查询SQL的时候要记得加条件AND deleted 0如果用的是MyBatis-Plus可以在全局配置里配好逻辑删除字段它会自动帮你拼上这个条件非常省心。提示这个设计细节在你的项目文档和答辩PPT里一定要提。老师问“你这个系统怎么防止误删数据”你答“我们采用了逻辑删除机制”这就是一个很好的加分点。3. 后端核心实现SpringBoot是怎么把接口串起来的3.1 项目分层结构与启动入口SpringBoot项目拿到手第一步先看包结构。我的源码里把包分成了controller接收前端请求、service业务逻辑、mapper数据库操作、entity实体类、config配置类、common通用工具和返回值、security安全认证相关。这个分层不是摆着好看的。它的核心原则是“上层依赖下层下层不知道上层的存在”。Controller只负责“收参数、调Service、返回结果”不写任何业务逻辑。Service专注处理核心业务只在需要数据时调用Mapper接口。这样做的好处是如果以后想加一个缓存层只需要在Service里动手Controller完全不用改。启动类上必须有SpringBootApplication注解它实际上是一个组合注解包含了SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。你只需要在启动类上加上MapperScan(com.example.hrms.mapper)MyBatis就能自动扫描到所有的Mapper接口不用每个都加Mapper注解。3.2 登录认证与权限控制的最小实现一个管理平台第一件事就是登录认证。我在源码里用的是JWTJSON Web Token方案。JWT的核心思想是用户登录成功后服务端生成一个加密的Token串返回给前端前端每次请求都把这个Token放在请求头里带上服务端通过解析Token来确认用户身份。为什么不用传统的Session方案因为前后端分离架构下前端可能部署在A服务器后端部署在B服务器Session存在服务端内存里没法共享。而JWT是无状态的服务端不存任何东西只靠验签就能确认身份。JWT实现起来也很简单。用户登录时把用户名和密码接收过来用BCrypt算法校验密码注意密码绝对不能明文存储一定要加密校验通过后用JWT工具类生成一个TokenToken里可以放userId、userName、roleId这些关键信息。代码大概这样// 登录成功后生成JWT String token Jwts.builder() .setSubject(user.getUsername()) .claim(userId, user.getId()) .claim(roleId, user.getRoleId()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) // 24小时过期 .signWith(SignatureAlgorithm.HS256, secretKey) .compact();然后写一个拦截器或者过滤器拦截所有需要登录才能访问的接口。在拦截器里校验Token是否有效、是否过期然后把解析出来的用户信息放到请求上下文里方便后续业务代码获取当前登录人。这里要注意登录接口本身、静态资源这些路径一定要在拦截器里放行不然就会出现“还没登录就被拦下来导致没法登录”的尴尬情况。权限控制我用的是RBAC模型基于角色的访问控制核心是三张表用户表、角色表、权限表加上用户-角色关联表、角色-权限关联表一共五张。它的逻辑是用户不能直接绑定权限而是先给用户分配角色再给角色分配权限。比如“HR专员”这个角色可以查员工列表但不能删除员工“HR管理员”角色既能查也能删。这样改权限的时候只需要调整“角色-权限”关系不需要一个个改用户。3.3 员工模块的增删改查如何写才算合格员工模块是所有课设里必然有的模块但很多人的写法停留在“能跑就行”。我建议你按照“加分标准”来写。Controller层接收参数后一定要做参数校验用Validated注解比如姓名不能为空、手机号要符合格式。Service层要处理核心业务逻辑例如新增员工时员工工号要自动生成建议按照“EMP当前时间戳的后六位”这种方式生成保证唯一性。分页查询是必考的点。MyBatis-Plus提供了一个非常方便的分页插件代码量极少IPageEmployee page new Page(currentPage, pageSize); LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), Employee::getName, name) .eq(deptId ! null, Employee::getDeptId, deptId) .orderByDesc(Employee::getCreateTime); employeeMapper.selectPage(page, wrapper);这段代码里面的like和eq方法第一个参数是boolean条件条件为true才拼上这个查询条件。这样搜索时用户填了什么就查什么没填就不加条件非常优雅地处理了动态查询问题。还有一个细节如果你不是用MyBatis-Plus而是原生MyBatis分页要手写LIMIT ? OFFSET ?还容易出错。所以课设阶段我非常推荐MyBatis-Plus它兼容MyBatis学习成本又低写代码效率高得多。唯一要注意的是进行连表查询的时候最好还是写原生的SQL因为MP的Wrapper封装不适合复杂的多表关联。3.4 考勤和薪资模块的常见业务难点考勤模块最核心的一个问题就是怎么判断迟到早退我在具体实现的时候配置文件里放一个上下班时间的配置项比如上班时间是09:00:00。查询考勤列表时用Java的LocalTime去比较员工打卡时间和标准时间// 判断是否迟到 LocalTime standardTime LocalTime.parse(09:00:00); LocalTime clockIn record.getClockInTime(); if (clockIn.isAfter(standardTime)) { record.setStatus(迟到); }薪资模块有一个非常经典的坑就是小数精度问题。如果直接用double类型存金额会出现“0.10.20.30000000000000004”这样的情况这在财务系统里是绝对不允许的。我在源码里所有涉及金额的字段都用BigDecimal运算时使用BigDecimal.valueOf()和.multiply()这样的方法严谨一点还可以设置一个工具类统一处理金额运算。你只要记住一句话涉及钱永远用BigDecimal不要用double和float。4. 前端核心实现Vue是怎么把数据渲染成页面的4.1 前端项目目录结构怎么组织拿到前端源码第一步先看src目录下的结构。我的组织方式是api文件夹放所有和后端交互的JS文件router文件夹放路由配置store文件夹放Vuex状态管理views文件夹放页面级组件components文件夹放通用组件。这里重点说api文件夹。我习惯每个模块建一个独立的JS文件员工模块对应employee.js里面封装了和后端接口一一对应的函数。比如// api/employee.js import request from /utils/request export function getEmployeeList(params) { return request({ url: /api/employee/list, method: get, params }) } export function addEmployee(data) { return request({ url: /api/employee, method: post, data }) }这样做的价值在于页面组件里不需要出现任何URL字符串全部集中管理。后端接口路径变了你只需要改这一个文件不用担心全局搜索不到。4.2 axios封装与Token携带前端和后台打交道全靠axios但直接用原生axios会有一个麻烦每次请求都要手动加上Token。而且如果后端返回401状态码Token过期你还要在每个页面单独处理跳转登录。所以我在源码里做了一层统一封装写在utils/request.js里面。关键在于请求拦截器和响应拦截器的配置。请求拦截器在发出请求前从Vuex或者localStorage里取出Token放到header里request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })响应拦截器则统一接收后端返回的数据结构。后端协议我定义的是Result结构包含code、message、data三个字段code为200表示成功为401表示未登录或者登录过期。拦截器发现code是401时直接跳回登录页并弹出消息提示避免每个页面写重复代码。这就是工程化思维的体现。4.3 员工管理页面实操表格、弹窗、表单三件套员工列表页是Vue最经典的“三件套”组合一个搜索区一个数据表格一个弹窗表单。我拿这个页面给你详细拆解一下Vue组件的实现思路。搜索区绑定一个searchForm对象里面放name和deptId两个字段。点击搜索按钮时调用后端接口传入这些参数。数据表格用Element-UI的el-table组件绑定:datatableData。注意每个操作按钮如果权限要求是可配的可以加上v-permission自定义指令来控制显示隐藏。弹窗表单是新增和编辑共用的同一个组件。关键技巧是新增和编辑的区别只在于是否传了id。弹窗打开时如果有id就调详情接口把当前记录数据回显到表单里没id就是空白表单。保存时根据是否有id来决定调新增接口还是更新接口。这样一套代码干两件事代码量直接减半handleEdit(row) { this.dialogVisible true this.form { ...row } // 把这一行数据拷贝到表单 }, handleAdd() { this.dialogVisible true this.form {} // 空表单 }表单保存前记得做校验。我用Element-UI自带的rules校验规则比如姓名必填、手机号正则校验这些规则是前端拦截用户输入的第一道防线。虽然后端也有校验但前端校验能给用户更好的体验不用等请求发出去再提示“格式错误”。4.4 路由守卫与页面访问控制vue-router默认情况下是可以直接访问任意页面URL的。但对一个管理系统来说如果用户没登录直接输入网址访问首页应该被拦回登录页。这就是路由守卫要做的事。我在router/index.js里配置了全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else { if (token) { next() } else { next(/login) } } })更进一步还可以在后端返回的菜单数据里控制左侧边栏菜单的渲染。也就是说同一个前端代码不同角色登录后看到的菜单项不一样。HR专员登录后只看到员工管理和考勤管理看不到系统管理。这个功能是用动态路由实现的稍微复杂一些但做出来后在答辩时讲给老师听老师会觉得你考虑得很周全。5. 本地部署实操从零到能看到登录页5.1 环境准备清单在你准备把项目跑起来之前先把环境检查一遍。这套项目在如下环境验证过JDK 1.8建议用8兼容性最好、Maven 3.6以上、Node.js 14到16如果你用的Vue2Node版本太高会碰到各种奇怪问题、MySQL 5.7或8.0均可。注意这里有个大坑。如果你本机装的是Node 18、甚至Node 20安装Vue2项目依赖的时候非常容易报错因为node-sass这个库需要针对不同Node版本编译。解决方案是用Node 14或者把package.json里的node-sass改成sassdart-sass然后重新npm install。后端准备工作用IDEA打开后端工程等待Maven下载完成依赖。然后把application.yml里的数据库用户名和密码改成你自己的。创建数据库后导入项目里附带的hrms.sql文件这个文件里包含了全部建表语句和初始数据默认管理员账号admin/admin123。前端准备工作用VS Code打开前端工程在终端执行npm install。如果你在国内这一步建议先执行一下给npm换成淘宝镜像源npm config set registry https://registry.npmmirror.com npm install5.2 启动步骤与验证方式第一步启动后端。在IDEA里找到Application启动类右键运行。看到类似“Started Application in xxx seconds”的日志输出说明启动成功。为了确认可以在浏览器访问http://localhost:8080/api/system/test如果返回一个JSON字符串说明后端接口已经通了。第二步启动前端。在前端目录下执行npm run serve看到“Compiled successfully”并且终端提示运行在http://localhost:8081就说明前端也起来了。这里有个常见情况后端的端口和前端端口不能冲突。我把后端设为8080前端设为8081这样正好。第三步验证登录。访问http://localhost:8081输入管理员账号密码如果能看到左侧菜单栏和主页面数据恭喜你整个项目已经跑通了。后续开发的时候每改完一个接口或者页面都不用重启服务后端的SpringBoot DevTools和后端的Vue热更新会自动生效能明显提升开发调试效率。5.3 打包部署怎么把项目变成可运行的产品课设交付的时候除了给源码最好还能给一个演示环境。后端打包很简单在项目根目录执行mvn clean package -DskipTests会在target目录下生成一个jar包。运行这个jar包用java -jar hrms-0.0.1.jar后端的服务就起来了。如果是部署到服务器上记得把数据库连接配置改成服务器上的MySQL地址。前端打包命令是npm run build打包完成后会生成一个dist目录。当你开发完、打完包之后如果你想直接用一个独立的web服务器来跑前端可以把dist目录里的文件交给nginx或者部署到IIS等静态服务器。但如果只是课设演示自己在本地看效果其实不必非要包一层web服务器在项目开发环境用node内置服务运行是最省事的。6. 常见问题与排查技巧实录6.1 项目启动报错速查表我把实际带学生过程中常见的启动报错问题整理成一个表给你对照着查报错现象根本原因解决方式启动时提示数据库连接refusedMySQL服务没启动或密码配置不对检查MySQL服务检查application.yml中密码前端npm install报node-sass错误Node版本过高不兼容降级到Node 14或把node-sass替换为sass后端启动提示端口被占用8080端口被其他程序占用关掉占用程序或改application.yml中的server.port启动后访问接口返回404后端Controller的路径没匹配上检查类上RequestMapping和方法的路径Mapper注入失败页面报错mapper接口没被扫描到启动类加上MapperScan(你的mapper包路径)登录提示用户名或密码错误密码加密后无法复现删除该用户重新新增用BCrypt加密密码6.2 前后端联调时几个容易踩的坑前后端联调是课设阶段最常见的“翻车”现场。第一个坑是跨域问题。浏览器会拦截不同端口之间的AJAX请求。Vue前端在8081端口后端在8080端口直接请求会被CORS拦住。解决方式有两种在后端配置CorsFilter或者在Vue的devServer里配置proxy代理。我用的是比较省事的后端全局CORS配置代码写在config里一个类搞定。第二个坑是日期格式问题。后端返回的日期是2024-05-06T10:30:00这种格式前端el-date-picker组件显示出来是英文格式或者直接显示NaN。我推荐在全局统一配置Jackson的日期序列化方式让后端统一返回yyyy-MM-dd HH:mm:ss格式的字符串。前端拿到字符串直接展示不自己处理。第三个坑是接口返回结果不能直接当数据用。你必须先养成习惯通过控制台看完整返回的结构找到真正数据的字段名再把它通过赋值处理到前端变量。我在前端实现时每一步都做了严格的数据解包处理避免错误访问undefined属性不然页面上展示出来就是空白的。6.3 答辩前一定要理解透的3个关键点这也是我个人给课设学生最常说的一点。做课设不只是代码能跑起来就万事大吉。答辩的时候老师一定会问几个问题。第一个就是“你这个项目的权限控制是怎么实现的”。如果你答不上来等于白做。所以JWT和RBAC这两个概念要背熟、理解透。第二个问题是“你遇到过什么难点是怎么解决的”。这时候一定不要说“好像没什么难点”要主动拿出我们上面讲的某一个坑比如BigDecimal精度问题、前端格式显示异常等把解决思路讲清楚老师会立刻给你加分。第三个问题是“如果让你扩展一个功能你怎么做”。这是最考验系统设计能力的问题。我的建议是准备一个自己能讲半小时的扩展方案。比如“我想给考勤模块加上请假审批流程涉及新建请假表、审批记录表还要加一个审批页面用状态机控制流转状态”。能聊出这么具体的内容说明你真的对这个系统理解到位了。7. 个人经验与下一步可以怎么玩最后再分享一点自己的体会。SpringBootVue做毕业设计其实不在于功能有多炫而在于你有没有把整个开发流程吃透。我在实际调这个项目的过程中最深的感觉是当你把后端接口写好、前端页面联调通过、数据在页面上正常渲染出来的那个瞬间之前踩过的所有坑都值得了。如果你拿到这套源码我建议不要只满足于“能跑通”。可以试着改一改给员工信息加上文件上传比如头像给考勤模块加一个地图打卡用高德地图API或者把系统管理里的菜单管理改成动态权限加载。这些改造的每一步都是在为你自己的技术能力添砖加瓦。等到答辩演示的时候你可以很坦然地说这个系统从数据库设计到前后端联调每一个环节我都亲手实现过。老师如果再追问点什么你也能接得住。这套源码就是你的一个完整的练手项目把它吃透你的实践能力会比只看视频刷题扎实得多。