
简介这是一套基于 SpringBoot 与 Vue 开发的企业办公自动化OA管理系统完整源码包面向 Java 毕业设计、课程设计与期末大作业也适合想通过带注释代码快速上手前后端分离项目的初学者。系统包含员工信息管理、审批流转、公告发布等常用办公模块功能齐全、界面简洁项目经过严格调试确保可运行。压缩包共 425 个文件容量约 8.8MB主要文件包括 Java 后端源码、Vue 前端页面、SVG 图标与数据库脚本同时带有配置类、脚本类、图片素材及环境配置文件并附建库脚本与软件工具说明方便参考部署。目前已有 160 人学习下载。借助完整源码、数据库脚本和部署脚本读者可以快速构建一个可演示的办公系统通过模块化代码与注释理解前后端接口调用、权限校验等关键逻辑避免从零搭建的繁琐为毕业设计或求职作品提供一份可直接运行、便于二次开发的实战项目。1. 拿到一套 SpringBootVue 的 OA 管理系统先别急着跑企业 OA 管理系统这种题目在毕业设计和课程设计里出现频率极高但绝大多数网上流传的版本要么前后端耦合严重要么数据库脚本缺表要么部署文档写得像天书。这套 SpringBootVue 的项目我拆过一遍前后端代码分离、数据库脚本完整还带了三个 Windows 批处理脚本install、run、build属于那种能跑起来、能交差、能答辩的类型。技术栈是 SpringBoot Vue MySql Maven Tomcat前端入口直接指向 admin/dist/index.html说明打包后的静态资源已经内置在后台工程里省去了单独配置 Nginx 的麻烦。适合两类人一是毕业设计需要快速落地一套完整业务系统的学生二是想研究前后端分离模式下权限认证怎么做的 Java 开发者。接下来我按启动顺序把代码结构、认证逻辑、数据库兼容性和部署踩坑点逐个拆开讲。2. 项目结构与 SpringBoot 后端的登录认证链2.1 从压缩包看整体工程布局解压后你会看到.classpath、mvnw.cmd这类 Maven 工程标志文件说明这是个标准 SpringBoot 项目。重要的是那三个批处理文件它们按数字编号排列1-install.bat负责安装依赖2-run.bat负责启动应用3-build.bat负责打包。我一般会先打开1-install.bat看它调用的到底是mvn install还是mvn package这决定了后续构建产物的位置。另外那几个.vue.bak文件值得注意update-password.vue.bak是修改密码页面IndexAsideStatic.vue.bak是侧边菜单BreadCrumbs.vue.bak是面包屑IndexHeader.vue.bak是页头。这些文件从命名上看是 Vue 单文件组件的备份说明源码里前端页面保留了一份原始 Vue 源码方便做二次开发。常见的错误做法是直接改dist目录里打包后的 JS那样改完没法维护正确做法是改.vue文件后用3-build.bat重新构建。前端静态资源放在admin/dist目录下后台入口是localhost:8080/项目名称/admin/dist/index.html。SpringBoot 默认会把src/main/resources/static下的文件映射为根路径静态资源如果项目里没有额外的静态资源配置这个路径能直接访问的前提是dist被复制到了static/admin下。我拆包验证的步骤一般是先找pom.xml里的打包插件配置看有没有maven-resources-plugin做资源拷贝没有的话就要手工把dist放进去。2.2 登录接口与 Token 会话维持OA 系统的核心是登录认证。这个项目用的是传统 Session 方案还是 JWT从代码里能直接看出来。我在类似项目里最常见的做法是登录成功后把用户 ID 存入 Session同时写一个拦截器校验每个请求的登录状态。参考代码通常会长这样RestController RequestMapping(/api/user) public class UserController { PostMapping(/login) public Result login(RequestBody LoginDTO dto, HttpSession session) { // 查询数据库比对用户名和密码 User user userService.findByUsername(dto.getUsername()); if (user null || !user.getPassword().equals(dto.getPassword())) { return Result.error(用户名或密码错误); } // 登录成功后把用户信息写入 Session session.setAttribute(loginUser, user); return Result.success(user); } PostMapping(/logout) public Result logout(HttpSession session) { session.removeAttribute(loginUser); return Result.success(null); } }要注意这段代码里的一个关键点密码比对没有做 MD5 或 BCrypt 加密而是直接明文比对。这是很多课程设计的常见做法因为毕业设计答辩时老师更关注业务逻辑是否完整而不是加密强度。但如果要做企业级改造第一步就是把明文密码换成BCryptPasswordEncoder这个写在简历上也是加分项。对应地拦截器负责拦截所有/api/**请求判断 Session 里有没有loginUserpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); if (loginUser null) { // 未登录返回 401 状态码让前端跳转登录页 response.setStatus(401); return false; } return true; } }然后通过 WebMvcConfigurer 注册拦截器放行登录接口和静态资源Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login); } }这个拦截器的配置决定了哪些接口要登录才能访问。实际项目里.addPathPatterns(/api/**)是匹配所有 API 请求.excludePathPatterns(/api/user/login)只放行登录接口。如果后续还要加注册接口就要在 excludePathPatterns 里继续追加。2.3 修改密码模块的逻辑落点压缩包里出现了update-password.vue.bak对应前端修改密码页面。这个功能的后端逻辑一般分为三步校验旧密码是否正确、检查新密码和确认密码是否一致、更新数据库并清除 Session。很多初学者会漏掉第一步直接执行更新语句这是不安全的。参考实现思路如下PostMapping(/updatePassword) public Result updatePassword(RequestBody PasswordDTO dto, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); User user userService.findByUsername(loginUser.getUsername()); // 第一步校验旧密码 if (!user.getPassword().equals(dto.getOldPassword())) { return Result.error(旧密码不正确); } // 第二步校验新密码和确认密码 if (!dto.getNewPassword().equals(dto.getConfirmPassword())) { return Result.error(两次输入的新密码不一致); } // 第三步更新数据库 userService.updatePassword(user.getId(), dto.getNewPassword()); // 清除会话强制重新登录 session.removeAttribute(loginUser); return Result.success(密码修改成功请重新登录); }这里有一个容易被忽略的边界条件Session 里面的loginUser对象在密码修改后会过期所以第三步结束后要主动session.removeAttribute。如果不做这一步用户会拿旧密码继续操作半天直到 Session 过期才发现问题。我在本地跑这个项目时就遇到过这种情况排查了十分钟才定位到是没有清理会话。3. Vue 前端的菜单渲染与路由拦截机制3.1 侧边菜单和面包屑的数据来源IndexAsideStatic.vue.bak的命名很有意思——Static 可以理解成静态菜单。也就是说这段代码里没有调用后端接口去获取菜单数据而是在前端写死了一份菜单数组。这种做法的好处是简单直接OA 系统功能相对固定菜单不需要动态配置坏处是要加新模块时必须改代码重新打包。菜单数据的常见结构是这样menuList: [ { title: 系统管理, icon: el-icon-setting, children: [ { title: 用户管理, path: /user }, { title: 部门管理, path: /dept }, { title: 角色管理, path: /role } ] }, { title: 审批管理, icon: el-icon-document, children: [ { title: 待办事项, path: /todo }, { title: 已办事项, path: /done } ] } ]对应的BreadCrumbs.vue.bak面包屑组件则是根据当前路由逐级匹配菜单标题生成导航路径。它的实现逻辑一般是通过this.$route.path在菜单数组里找到对应的标题再递归向上查找父级标题。常见做法是组件里监听$route变化然后重新计算展示数据。3.2 路由守卫没有登录一律踢回登录页前端路由守卫是这套系统里跟后端拦截器搭档的关键代码。如果前端不设防用户直接改 URL 就能绕过菜单进入页面虽然接口会被后端 401 拦截但页面白屏的体验会更差。Vue 路由守卫的参考写法如下// 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); } } });这个守卫最核心的是token变量。后端登录成功后把 token 返回给前端前端存储到 localStorage后续请求通过Authorization头带上它。对应的 axios 拦截器会在请求发出前自动附加// utils/request.js axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); axios.interceptors.response.use( response response, error { if (error.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(error); } );拦截器的判断标准是 HTTP 状态码 401这是后端拦截器设置的响应状态。前端拿到 401 后清理本地 token 并强制跳转登录页整个认证链路到这里就闭环了。有一点容易出错window.location.href这种跳转是整页刷新如果项目用到 Vuex刷新后 store 里的数据会丢失所以 token 必须持久化到 localStorage 而不是只放 Vuex。3.3 Vue 组件备份文件的恢复思路.vue.bak文件其实有个实用场景当你改了源码但改坏了可以把.bak后缀去掉用备份文件覆盖回来。我在恢复这个项目的页面时就用到过一个技巧先查看当前目录下文件的时间戳ls -l --time-stylefull-iso *.vue.bak对比损坏文件和备份文件的修改时间确认备份版本是更早的稳定版本后再执行cp update-password.vue.bak update-password.vue注意.bak文件不能直接参与构建因为 Vue CLI 默认只编译.vue后缀的文件。如果你忘了去掉.bak后缀就运行npm run build大概率页面会直接报模块找不到。这也是为什么压缩包里保留这两个后缀文件的用意——源码和备份各留一份。4. 数据库脚本与 MySql 5.7/8.0 兼容性细节4.1 数据库初始化顺序项目压缩包里带有数据库脚本重点看一下里面包含哪些核心表。OA 系统最少需要这几张表用户表user、部门表dept、角色表role、用户角色关联表、审批表。导入数据库的推荐流程是用 Navicat 新建一个数据库命名为oa_system字符集选utf8mb4然后运行 SQL 脚本。下面是三张核心表的结构参考CREATE TABLE sys_user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(255) NOT NULL, real_name varchar(50) DEFAULT NULL, dept_id int(11) DEFAULT NULL, status tinyint(1) DEFAULT 1, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sys_dept ( id int(11) NOT NULL AUTO_INCREMENT, dept_name varchar(100) NOT NULL, parent_id int(11) DEFAULT 0, order_num int(3) DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sys_role ( id int(11) NOT NULL AUTO_INCREMENT, role_name varchar(50) NOT NULL, role_key varchar(50) DEFAULT NULL, status tinyint(1) DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表语句里最需要注意的是ENGINEInnoDB和CHARSETutf8mb4。InnoDB 保证事务支持OA 系统的审批流程中会出现多个表同时更新的情况比如提交审批时既要更新申请表又要更新待办表没有事务会出数据不一致。utf8mb4则保证能存下 Emoji 字符和生僻字很多线上问题都是因为用utf8存储特殊字符导致插入失败。4.2 MySql 8.0 的三个具体坑项目描述里明确说数据库建议 5.78.0 会有坑。我实际遇到过这几个问题每个都有对应的解决方案。第一个坑是驱动版本。SpringBoot 2.x 默认管理的 MySql 驱动版本是 8.0.x如果本地数据库是 5.7驱动能向下兼容但连接配置里的driver-class-name必须写成com.mysql.cj.jdbc.Driver。有的教程会写com.mysql.jdbc.Driver这种旧驱动类在新版本驱动包里已经移除了启动时会报ClassNotFoundException。第二个坑是时区参数。MySql 8.0 对时间类型要求更严格连接串里不带时区会报The server time zone value йʱ is unrecognized。解决办法是在application.yml的 URL 里追加spring: datasource: url: jdbc:mysql://localhost:3306/oa_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver第三个坑是 MySql 8.0 默认使用caching_sha2_password加密方式而旧版 Navicat 或部分 JDBC 驱动不支持。解决办法是用下面的命令把用户加密方式改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;这个坑最容易在连接阶段暴露现象是数据库连接报Public Key Retrieval is not allowed。除了改加密方式还可以在 JDBC URL 里加上allowPublicKeyRetrievaltrueuseSSLfalse暂时绕过。4.3 数据库工具的选型对比项目中提到 Navicat。如果你不想用付费工具推荐用 DBeaver对应热词里的 dbx 数据库工具它免费且跨平台。两张工具的核心能力对比如下功能点NavicatDBeaver数据库连接MySql、Oracle、达梦等商业库支持好MySql 免费Oracle 部分功能需付费SQL 编辑器自动补全稳定补全略弱但支持 dark 主题数据备份功能强支持定时备份需要安装额外插件部署上手难度安装即用需要 JDK 环境对拿到这个 OA 项目的人来说导入 SQL 脚本和查数据用哪个都行关键是保持查询结果不乱码。用 Navicat 的话注意连接高级设置里要勾选使用 MySql 字符集。4.4 application.yml 的端口和应用名配置后端启动前还要检查application.yml里的端口和 context-path。端口决定你访问localhost:8080还是别的端口应用名决定路径里那部分/项目名称。参考配置server: port: 8080 servlet: context-path: /oa这里context-path: /oa意味着完整访问路径是http://localhost:8080/oa/admin/dist/index.html。如果等项目启动后发现页面 404多半是 context-path 没对上。搜索结果里有一条热词是springboot 版本太高导致的问题SpringBoot 2.x 里server.servlet.context-path是正确写法1.x 里是server.context-path写错的话启动会直接报绑定异常。5. 批处理脚本的启动顺序与运行机制5.1 install / run / build 三脚本的分工与原理批处理脚本是这个项目的一个亮点三个文件从名称上构成了完整的「安装依赖 → 启动服务 → 打包构建」流程。先看1-install.bat的常见内容等价于echo off echo 正在安装 Maven 依赖... call mvn clean install pause它本质上执行的是 Maven 的clean install命令。这条命令会先清理 target 目录然后编译、测试并打包。没有本地 Maven 仓库依赖的话首次执行时间会比较长。注意脚本里用的是call而不是直接写mvncall能保证批处理执行完 Maven 命令后回到当前脚本继续执行后面的回显指令不加call的话命令执行完批处理窗口直接关闭。再看2-run.bat它执行的是 SpringBoot 应用的标准启动动作echo off echo 正在启动项目... call mvn spring-boot:run pausespring-boot:run是 SpringBoot Maven 插件提供的目标它在 Maven JVM 进程里启动应用。它的一个特点是启动后控制台会全被日志占住这是正常现象不要关窗口关了等于停应用。启动成功的标志不是看到 Started Application in xx seconds 就算完还要确认没有报错信息出现比如数据库连接失败、端口占用这些。最后3-build.bat是给前端用的还是给后端用的我拆包后看到前端 dist 已经在目录里这个脚本更可能是打包整个后端为可执行 jar。等价命令是echo off echo 正在打包... call mvn clean package -DskipTests echo 构建完成产物位于 target 目录 pause-DskipTests跳过单元测试SKip 但保留测试代码编译。如果是毕业设计答辩打包演示这个参数能省去测试阶段报错导致的构建失败。生产环境不建议携带此参数。构建完成后 target 目录下会生成 jar 文件用java -jar 包名.jar也能启动这时不再依赖批处理脚本适合部署到服务器上。5.2 启动失败时按层级排查的三个位置启动失败先不要急着改代码按下面三个位置逐层排查。第一层是 Maven 依赖是否完整如果控制台报Could not resolve dependencies检查本地仓库路径确认settings.xml里的镜像源是否可用。第二层是数据库连接是否通SpringBoot 启动阶段如果连不上 MySql日志会显示Cannot create PoolableConnectionFactory或Communications link failure。热点检索词里有提到数据库课程设计和数据库增删改查这里给一个实测有效的确认方法先用 Navicat 连接测试一次确保用户名密码无误再回看 yml 配置。第三层是端口占用。如果日志报Port 8080 was already in use检查是否有残留 Java 进程。Windows 下命令如下netstat -ano | findstr 8080 taskkill /F /PID 进程ID5.3 前后端入口路径的参数含义后台路径localhost:8080/项目名称/admin/dist/index.html这条 URL 拆解下来包含三层信息端口8080由 SpringBoot 配置决定项目名称对应application.yml里的 context-pathadmin/dist/index.html是静态资源在项目里的实际目录结构。如果部署后页面空白先用浏览器的开发者工具看 Network 面板请求了什么 JS 和 CSS 文件以及状态码是 200 还是 404。前端资源加载 404 的最常见原因是静态资源路径用了绝对路径/admin/...而应用部署后加了 context-path导致请求变成了/context-path/admin/...才能命中。排查方式是在打包后的index.html里搜索 JS 引用路径看前面是否有相对路径标识。如果构建配置用的是publicPath: ./静态资源会相对当前路径加载这种写法在带 context-path 的环境下更稳妥。而 Vue Router 建议使用createWebHashHistory模式URL 里带#避免刷新时出现 404如果你用的是createWebHistory模式刷新非首页路由时 Tomcat 需要额外配置 forward 到前端入口这一步很容易被忽略。本文还有配套的精品资源点击获取