ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+Vue居家办公管理系统源码解析:RBAC+JWT实战

2026/9/7 22:48:04 拓冰建站 浏览量
SpringBoot+Vue居家办公管理系统源码解析:RBAC+JWT实战 说实话我最初接触这类居家办公管理系统是帮一个朋友的公司处理远程协作需求。那时候团队散落在各处每天要汇总健康状态、交工作日报、安排临时任务Excel传来传去完全乱套才意识到这类系统并不是学校课设那么简单它背后是一整套业务流的线上化。这套SpringBoot Vue MySQL的源码项目就是在这样的场景下很适合拿来即用的骨架——后端是Java生态里最主流的SpringBoot前端是Vue Element UI的经典后台组合数据库用MySQL三件套都是国内Java从业者最熟悉的技术栈而且拿到了就能直接跑起来不用自己从零搭环境、写脚手架。这篇博文我就以拆解这套源码为线索把它的业务设计、技术实现、跑通步骤和二次开发思路一次讲透适合正在学前后端分离项目的同学也适合需要快速交付一个内部管理系统的开发朋友。1. 先看清这套系统的业务边界居家办公需要管什么很多初学者拿到一个项目源码第一反应是急着启动、急着看页面结果跑起来之后一脸懵不知道每个模块是干嘛的也不知道为什么要有这些表。我建议反过来先站在业务方角度问一句居家办公场景下一个信息管理系统到底要解决什么问题远程办公最大的痛点不是没法开会而是信息不透明员工今天身体状态怎么样工作进度到哪了临时布置的任务有没有人跟进请假审批找谁签这些原本在线下靠看一眼问一嘴就能解决的事一旦散到线上就需要有一个统一入口来承接。这套系统就是围绕这个诉求设计的所以你会发现它的功能模块非常聚焦健康打卡、工作日报、任务管理、公告通知、审批流程再加一套后台权限管理。1.1 三个核心角色与权限模型居家办公场景下天然存在三类人管理员、部门负责人、普通员工。这套系统在权限模型上走的是经典的RBAC基于角色的访问控制也就是用户-角色-菜单三层结构。管理员拥有全部菜单权限能管理系统内的用户、角色、菜单查看所有数据统计。部门负责人能看本部门员工的打卡和日报负责审批任务和请假申请。普通员工日常使用打卡、日报、查看公告、提交审批等。之所以用RBAC而不是给每个用户单独配权限是因为远程办公场景下人员流动性大、角色变更频繁。今天你是普通员工明天可能被临时指定为某个项目的负责人如果权限是写死在代码里的每次调整都要改代码、重新部署那维护成本就失控了。RBAC的好处在于权限变更只需要在数据库里改角色关联关系前端菜单会自动响应。1.2 从功能清单反推数据库表对应到数据库设计核心表包括这么几张表名用途关键字段sys_user用户表账号、密码、姓名、部门ID、状态sys_role / sys_user_role角色与用户关联角色ID、用户IDsys_menu / sys_role_menu菜单与角色关联菜单名、路由地址、权限标识health_report健康打卡表体温、健康状况、所在地、打卡日期work_log工作日报表日期、今日内容、明日计划、完成进度task_info任务表任务标题、内容、指派人、截止时间、状态notice_info公告表标题、内容、发布人、发布时间approval审批表审批类型、申请内容、状态、审批人这套表结构非常典型对新手来说是很好的参考资料它演示了一个后台管理系统从用户、权限到业务模块的完整建模方式。每一个字段为什么存在、类型为什么这么定都能在这套源码里找到答案。比如task_info表里一定会有assignee_id和status字段前者对应任务派给谁后者对应任务进行到哪一步缺一个这个模块就跑不完整。2. 后端SpringBoot认证、分层与核心接口的实现拆解看后端代码我习惯按入口 → 认证 → 业务接口的顺序去读。这套源码的包结构是标准的三层架构看懂了它等于看懂了一大半SpringBoot商业项目的组织方式。2.1 工程分层与包结构设计打开源码你能看到这样的目录结构src/main/java/com/example/hrm/ // 居家办公管理系统 ├── controller/ // HTTP请求入口层 ├── service/ // 业务逻辑层 ├── mapper/ // MyBatis-Plus数据访问层 ├── entity/ // 数据库实体类 ├── config/ // 配置类跨域、拦截器、分页插件 ├── common/ // 统一返回Result、异常处理、常量 ├── utils/ // JWT工具、密码加密工具 └── HrmApplication.java // SpringBoot启动类为什么要这样分层核心目的是各司其职互不越界。Controller层只做三件事接收参数、调用Service、返回Result。它不写业务逻辑也不直接操作数据库。Service层承载核心业务规则比如同一天只能打一次卡审批只能由部门负责人操作。Mapper层只负责数据库的增删改查配合MyBatis-Plus大部分单表操作连SQL都不用写。这么拆分之后遇到问题定位非常快接口参数不对去Controller看业务规则不对去Service看SQL慢去Mapper看。如果所有逻辑都堆在Controller里前期写起来是爽后期改起来是灾难。2.2 认证方案JWT令牌的签发与校验前端项目是Vue后端是SpringBoot这种前后端分离架构下Session机制并不好用因为前端和后端可能部署在不同的域名、不同的端口上Session的跨域处理很麻烦。所以这套源码用的是JWTJSON Web Token来做无状态认证流程是这样用户登录后端校验账号密码密码是BCrypt加密存储的不是明文。校验通过后后端生成一个JWT字符串返回给前端。前端把Token存到localStorage后续每个请求都在Authorization请求头里带上Bearer token。后端通过拦截器解析Token取出用户ID和角色信息放入当前请求上下文。JWT的核心价值是无状态。服务器不需要保存会话信息Token本身包含了用户身份和过期时间校验时只要验签通过、没过期就认为是合法请求。这对横向扩展特别友好——如果以后要部署多台后端服务器做负载均衡JWT方案几乎不用改代码Session方案还得引入Redis做统一会话存储。拦截器这块有个细节值得学习除了校验Token是否有效还会把用户权限标识比如admin、dept_manager从Token中解析出来存到ThreadLocal或请求属性里后续业务代码直接通过工具类取当前用户即可不需要每个接口都重复解析一次Token。这种一次解析全局可用的做法是很多生产级项目的标准姿势。2.3 几个核心接口的实现思路拿健康打卡接口举例它的实现很能体现业务规则落地的过程PostMapping(/report) public ResultString report(RequestBody Valid HealthReportVO vo) { // 从请求头解析Token拿到当前登录用户ID Integer userId JwtUtil.getCurrentUserId(request); // 业务规则同一天只能打卡一次 boolean exists healthReportService.checkTodayReport(userId); if (exists) { return Result.error(今天已经打过卡了请勿重复提交); } healthReportService.saveReport(userId, vo); return Result.success(打卡成功); }注意几个细节Valid注解做参数校验体温不能为负数、日期不能为空等字段级校验在前端和后端各做一次前端校验是为了用户体验后端校验是安全底线。同一天只能打卡一次这个规则放在Service层而且通过数据库唯一索引user_id report_date兜底。为什么两个都要因为理论上存在并发请求同时通过checkTodayReport判断导致重复插入。数据库唯一索引能挡住最后一层保证数据绝对不重复。日报模块的提交接口则是另一个思路它不只是插入一条记录还要更新任务状态、记录操作日志所以整个方法加了Transactional事务注解。一旦中间任何一步失败所有操作全部回滚不会出现日报提交成功了但任务状态没更新的脏数据情况。这种对事务边界的把握是区分一个项目是否达到生产标准的重要标志。3. 前端Vue从动态菜单到接口拦截的完整闭环后端提供的是数据和服务前端负责把这一切变成可交互的页面。这套系统的前端是基于Vue 2 Element UI开发的经典后台管理模板如果你用过主流的中后台管理系统会对它的目录结构感到非常亲切。3.1 路由与导航守卫前端路由的核心在于权限控制。普通员工登录后左侧菜单不应该显示用户管理部门负责人登录后能看到本部门的数据统计。这套系统不是在前端写死哪些角色能访问哪些路由而是由后端根据用户的角色动态返回菜单列表前端再生成对应的路由和菜单。关键代码如下// 路由守卫每次跳转前检查登录状态 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else { if (!token) { next(/login) } else { // 如果已有token但没有菜单数据说明刚刷新页面需要重新拉取动态菜单 if (!store.state.menuList.length) { store.dispatch(generateMenu).then(() next({ ...to, replace: true })) } else { next() } } } })代码里generateMenu这个action做的事是调用后端/user/menus接口拿到当前用户可见的菜单列表动态注册到Vue Router中同时渲染到侧边栏。这样每次刷新页面后菜单都是重新拉取的不同角色看到的东西天然不同。这里有个小坑我第一次写动态路由的时候踩过路由必须用router.addRoutes动态添加但刷新后动态路由会丢失所以必须在路由守卫里明确判断菜单数据是否已经被加载否则会出现刷新后白屏的问题。3.2 axios封装与请求拦截前端所有HTTP请求都通过一个封装好的axios实例发出统一处理三个问题请求头自动携带Token。响应统一拦截code ! 200时自动弹出错误提示。遇到401Token过期或未登录时自动跳转登录页。const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.msg) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { router.push(/login) } return Promise.reject(error) } )这样封装的直接好处是每个业务页面调用接口时只需关心传什么参数、拿到什么数据不用重复处理Token、错误提示、登录跳转这些琐碎逻辑。我看到这套源码里所有页面都在用这个request实例这就是统一封装的价值——改动一处全局生效。3.3 表格页面的组件化实现打开源码你会发现打卡记录、日报列表、任务列表这些页面长得非常像顶部是查询条件中间是表格右侧是操作按钮。它们其实是同一套代码模式的重复实例化。拿健康打卡列表来说核心就三部分el-table绑定列表数据列字段与后端返回的JSON字段一一对应。顶部用el-formel-date-picker做日期范围筛选查询条件拼装成对象传给后端。分页用el-paginationcurrent-page和page-size变化时重新拉取数据。页面代码整体逻辑清晰而且把查询、重置、分页跳转这些行为统一抽取成了公共方法新加一个列表页面时复制粘贴改改字段就能用。对新手来说这一套表格实现过程非常值得敲一遍它就是后台管理系统里最典型的CRUD闭环前端请求接口、后端返回分页数据、前端渲染列表、用户点操作再请求接口。4. MySQL数据库设计初始化脚本才是可直接运行的关键很多人拿到一个项目跑不起来的第一大原因就是数据库初始化脚本有问题。这套源码能在标题里写上可直接运行很大程度要归功于它附带的SQL脚本考虑得比较周全。4.1 表结构设计要点打开SQL脚本你能看到几个值得学习的通用约定每张业务表都有id、create_time、update_time、deleted四个基础字段。create_time记录创建时间update_time记录最后修改时间deleted是逻辑删除标记0未删除1已删除。用逻辑删除而不是物理删除的好处是数据不丢哪天误删了还能恢复。配合MyBatis-Plus的自动填充功能插入和更新时这些字段会自动赋值业务代码里不需要手动维护。每张表都显式指定了字符集为utf8mb4。这一点特别重要因为MySQL 8.0默认字符集已经是utf8mb4但如果你用的是 MySQL 5.7默认往往是utf8mb3这意味着存不了Emoji表情。当初我遇到推送消息里带表情导致入库报错排查半天才发现是字符集问题。源码里统一写死DEFAULT CHARSETutf8mb4就避免了这种环境差异。关键表上有唯一索引。比如健康打卡表的唯一索引是uk_user_date(user_id, report_date)从数据库层面保证一个用户一天只能有一条打卡记录。业务代码里可以再加一层checkTodayReport校验但这种双保险的设计体现了对数据一致性的重视值得学习。看一段核心表的建表语句CREATE TABLE health_report ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, temperature decimal(4,1) DEFAULT NULL COMMENT 体温, health_status varchar(20) DEFAULT NULL COMMENT 健康状况, report_date date NOT NULL COMMENT 打卡日期, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint(1) DEFAULT 0 COMMENT 逻辑删除标记, PRIMARY KEY (id), UNIQUE KEY uk_user_date (user_id, report_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康打卡表;4.2 初始化数据的重要性脚本里除了建表语句还插入了不少初始数据。这部分很多人会忽略但恰恰是能否直接运行的决定性因素。第一是内置管理员账号。脚本里默认插入了admin用户密码是BCrypt加密后的密文并关联了管理员角色。也就是说你启动项目后直接用admin / 123456就能登录不需要去数据库手动造账号。这里有个细节值得说明密码存的是BCrypt密文而不是明文或MD5。为什么因为BCrypt是加盐哈希同样的密码每次生成的密文都不一样即使数据库泄露反查原文的难度也比MD5高很多。第二是角色和菜单的初始化数据。RBAC模型决定了用户登录后能看到什么菜单而这些菜单数据是存在数据库里的。如果脚本里没有初始化菜单数据前端动态路由generateMenu拉回来的就是空数组登录后会发现页面白茫茫一片。这个坑我见过不止一次。第三是几组演示业务数据。比如预先创建了几个部门、几个测试员工账号、几条打卡记录和日报目的是让界面一旦跑起来就有内容可看不会空荡荡的也方便你验证分页和查询功能。5. 本地跑通全流程SpringBootVueMySQL三端联调实录这部分我按实际跑通的顺序把步骤串一遍同时把容易出问题的点单独指出来。这套源码我手里跑过不止一次下面这些坑是真实遇到过的。5.1 环境准备清单跑这套项目前先把环境确认好工具推荐版本备注JDK1.8 或 11版本太高如17可能出现依赖兼容问题Maven3.6后端依赖管理工具Node.js14.x 或 16.x版本过高可能导致node-sass安装失败MySQL5.7 或 8.0记得安装时设置好root密码IDEIDEA 或 VS Code后端推荐IDEA5.2 后端启动步骤与常见报错第一步用IDEA导入后端项目目录等待Maven下载依赖。这里有个小技巧如果下载速度慢在settings.xml里配置阿里云Maven镜像能省特别多时间。第二步在MySQL中执行SQL脚本。可以用Navicat、DBeaver或者直接用命令行mysql -u root -p init.sql第三步修改application.yml里的数据库连接信息重点看这三行spring: datasource: url: jdbc:mysql://localhost:3306/hrm_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码第四步运行HrmApplication.java的main方法或者用Maven命令mvn spring-boot:run启动。常见的报错有这几个我都踩过Access denied for user rootlocalhost数据库账号密码配错了。注意application.yml里可能有多个环境配置dev、prod确认当前用的是哪一份。Unknown database hrm_dbSQL脚本只建了表可能没建数据库。要么手动CREATE DATABASE hrm_db要么在脚本最开头补上建库语句。The server time zone value is unrecognizedMySQL 8.0的时区问题在JDBC URL里加上serverTimezoneAsia/Shanghai即可。端口被占用后端默认跑在8081端口如果被其他程序占了改成别的端口同时注意前端proxy代理里要对应改。5.3 前端启动步骤与常见报错第一步在终端进入前端项目目录执行npm install这个命令会安装package.json里声明的所有依赖。它也是整个前端启动过程最不确定的一步主要看网络和Node版本。第二步依赖装完后启动开发服务器npm run dev默认端口一般是8080启动成功后浏览器访问http://localhost:8080。这一步我遇到过的典型报错是node-sass安装失败这是经典老坑。node-sass需要下载二进制文件容易卡住或报错。解决办法是换成sassdart-sass或者用淘宝镜像源安装再或者使用项目package-lock.json里锁定的Node版本。15以上版本装node-sass基本必挂建议用Node 14。Module build failed: Error: Cannot find module core-js依赖没装全删掉node_modules和package-lock.json重新npm install。5.4 前后端联调与跨域处理启动完前端和后端你会发现前端页面能打开但接口请求全报跨域错误。原因很直白浏览器默认不允许一个域名的页面去请求另一个域名的接口。前端跑在http://localhost:8080后端跑在http://localhost:8081跨域了。这套源码里解决的方案是后端加全局CORS配置类。代码类似这样Configuration public class CorsConfig { Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8080) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true); } }; } }用全局配置的好处是一次配好、全接口生效。如果你用的是Vue CLI自带的devServer也可以用proxy代理把/api开头的请求转发到后端两种方式都能解决但生产环境部署时更推荐前者后端配置CORS因为Nginx代理更常见、也更灵活。6. 拿到源码之后二次开发与部署上线的实际经验源码能跑通只是第一步真正有价值的是在它的基础上改造成自己想要的样子。我结合实践中常见的方向分享几个可行的思路。6.1 业务调整的切入思路把健康打卡改成通用考勤打卡。居家办公场景会过去但考勤需求长期存在。这个模块改起来成本很低把health_report表的temperature、health_status字段替换成check_in_time、check_out_time、address之类的打卡信息前端页面同步调整表单和表格列。后端接口的核心逻辑唯一索引防重复、今日打卡状态判断完全不用改。给审批模块接入工作流引擎。现在代码里的审批逻辑是简单的提交—审批两级模式如果部门层级多、审批流程复杂可以在此基础上集成Flowable工作流引擎。这个问题需要单独拉一个分支来做核心是把approval表从普通的业务表改成流程实例表把流程走到哪一步交给Flowable管理。加数据看板。居家办公时期管理者最关心的是今天多少人打卡了、多少任务完成了。源码里虽然有简单的统计接口但可以再扩展一个Dashboard模块用ECharts在首页展示打卡率趋势图、任务完成率饼图、各部门日报提交情况等。数据源就是已有的几张表写几个聚合查询的SQL即可。6.2 部署到服务器时要注意的事本地跑通和线上部署是两码事。本地用npm run dev启动的是开发服务器性能和稳定性都不适合生产环境。我建议按以下方式部署前端执行npm run build打包产出dist静态目录直接交给Nginx托管。后端用Maven打成JAR包mvn clean package -DskipTests java -jar target/hrm-system.jarNginx反向代理把/api开头的请求转发到后端服务端口同时托管前端静态文件server { listen 80; server_name yourdomain.com; # 前端静态资源 root /usr/share/nginx/html; index index.html; # 解决Vue Router的history模式刷新404问题 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时还有一个容易忽略的细节生产环境一定要用独立的配置文件比如application-prod.yml把数据库密码、端口等信息与开发环境分开避免把本地配置泄露到服务器上。另外后端上线前记得把CORS配置的allowedOrigins从http://localhost:8080改成你的正式域名否则跨域拦截会影响线上访问。如果你是自己拿这套源码学习我建议拿到手先不看Controller而是先跑起来、点一遍功能再去读代码。先有操作体验再去看底层实现效率会高很多。而且这套系统的权限设计很规范把RBAC配合JWT这套组合吃透以后不管对接什么样的后台管理项目都会比别人更快上手。