资讯详情

企业OA系统源码实战:SpringBoot2+Vue3+MyBatis-Plus全解析

📅 2026/10/19 8:05:25 | 华诺云谱 👁 阅读
企业OA系统源码实战:SpringBoot2+Vue3+MyBatis-Plus全解析
这几年接企业级系统开发的活儿“OA办公自动化”绝对是一个绕不开的品类。你说它技术上有多高深真不见得。但你说它好做同样不见得。它最有意思的地方在于深度绑定企业的真实业务流程组织结构、岗位权限、审批链路、消息触达哪一块没设计好上线就是一地鸡毛。这套SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 的企业OA管理系统源码属于当下Java Web开发里非常典型的一套组合。技术选型不炫技、不浮夸胜在稳定、生态好、上手快。这篇博文我不打算只把功能列表念一遍而是想从源码结构、业务设计、环境跑通、二次开发、部署避坑这几个维度带你把一套OA源码真正“吃透”——既能当学习项目深入拆解也能直接拿来改造成能用的产品。适合看这篇文章的人我觉得有三类毕业设计想选OA题目的学生、准备接企业信息化外包的开发者、以及公司内部想自建办公系统的运维或后端同学。无论你属于哪一类这套技术栈和业务模型都有很强的借鉴意义。1. 项目全景与技术栈拆解1.1 为什么是这个组合选型背后的现实逻辑先说结论这套组合是当前国内中小型企业管理软件开发里性价比最高、招人成本最低、排错最容易的方案之一。SpringBoot2 虽然在2024年底停止商业支持但大量存量项目的生产环境仍然跑在2.x上。它相比SpringBoot3最大的优势是兼容性宽JDK8/11都能跑老代码迁移成本极低大部分第三方starter都有对应版本。选2.x不是落后是稳妥——尤其OA这种重业务、轻创新、追求长期稳定运行的系统稳妥比什么都重要。Vue3 代表着前端的组件化开发趋势Composition API 在代码组织上比Vue2的Options API更清爽配合 Vite 开发和构建速度都有明显提升。实际体验下来组件封装和状态管理的留活空间也更大。这套系统里用 Vue3 Element Plus大概率后台管理的表格、弹窗、表单这些重交互场景Element Plus 几乎覆盖90%以上的需求。MySQL8.0 在数据字典、窗口函数、JSON字段支持上都比5.7完善不少。特别是窗口函数在OA的考勤排名、报表统计里非常实用。8.0默认的 utf8mb4 字符集处理中文、表情符号都没有压力这在企业通讯录、审批意见这些高频中文文本场景里属于硬需求。MyBatis-Plus 是这套系统里我比较喜欢的一个组件。它把单表CRUD直接干掉了一半BaseMapper自带方法就够用分页用内置插件代码生成器十分钟出一套模块骨架。最关键的是它“可进可退”复杂SQL你仍然可以手写XML不会被框架绑架。表格对比一下这套技术栈和常见替换方案的差异你就知道它有多“稳”技术点本项目方案替换方案替换代价后端SpringBoot2SpringBoot3需升级JDK17部分starter兼容要处理前端Vue3 ViteVue2 Webpack组件API和构建链差异大改造成本高ORMMyBatis-PlusJPA / 原生MyBatisJPA学习曲线陡原生MyBatis开发效率降低数据库MySQL8.0PostgreSQL / SQL Server函数差异较多SQL要整体过一遍1.2 一个完整的OA系统到底包含什么业务模块刚接触OA项目的人容易陷入“OA就是请假审批”的误区。实际上一个能真正落地交付的OA系统业务域比你想的宽得多。按这套源码的常规模块划分大体包含以下核心板块系统管理用户管理、角色管理、菜单管理、部门管理、字典管理、操作日志审批中心请假申请、报销申请、用章申请、合同审批、自定义流程公文管理公告发布、公司新闻、文件收发、内部通知会议管理会议室预定、会议通知、会议纪要考勤管理打卡记录、请假统计、加班申请、月度出勤报表任务协作任务派发、进度跟踪、任务督办个人中心待办事项、已办事项、我的申请、消息通知你自己对比一下就会发现系统管理是地基审批中心是核心其余全是企业里真实的高频场景。任何一个模块抽出单表模型都可以作为MyBatis-Plus和Vue3前后端联动的最佳练手案例。我接触过不少甲方他们采购或者自研OA时最常问的前三个功能就是“审批流能不能灵活配”“权限能不能细化到按钮”“移动端能不能直接办公”。这说明OA项目的核心竞争力从来不在技术复杂度而在业务贴合度和模型的扩展性。1.3 拿到源码后第一个该看的东西目录结构先不急着跑环境把源码的骨架摸清楚后面处理问题才有方向感。一套标准的SpringBoot Vue3前后端分离项目目录一般长这样oa-system ├── backend/ # 后端工程 │ ├── src/main/java │ │ ├── com/company/oa │ │ │ ├── common/ # 通用工具、常量、异常处理 │ │ │ ├── config/ # 配置类MyBatis-Plus、拦截器、Cors │ │ │ ├── controller/ # 接口层 │ │ │ ├── service/ # 业务逻辑层 │ │ │ ├── mapper/ # 数据访问层 │ │ │ ├── entity/ # 实体类 │ │ │ └── security/ # 认证与权限核心 │ │ └── resources │ │ ├── mapper/ # MyBatis XML文件 │ │ └── application.yml │ └── pom.xml ├── frontend/ # 前端工程Vue3 Vite │ ├── src/ │ │ ├── api/ # 接口封装 │ │ ├── router/ # 路由配置 │ │ ├── store/ # 状态管理Pinia/Vuex │ │ ├── views/ # 页面组件 │ │ └── utils/ # 请求封装、工具方法 │ └── package.json └── doc/ # 文档目录数据库脚本、接口说明等我的习惯是先读entity和mapper层因为实体类和数据表是一一对应的看一圈就知道系统有哪些数据、各表什么关系。然后看service层的核心业务方法理解审批状态流转、权限校验这些关键逻辑。最后才看controller的接口暴露方式以及前端页面如何对接。2. 核心架构与业务设计深度分析2.1 前后端分离下的数据流转链路这套OA系统采用的是标准的单体应用 前后端分离架构。浏览器里的Vue3应用通过HTTP请求访问后端接口后端返回JSON数据前端负责渲染和交互。一次完整的用户操作数据流转大概是这样浏览器输入用户名密码 → 登录页调用后端登录接口 → 后端校验通过返回JWT令牌 → 前端把令牌存在本地存储中 → 后续每次请求都在请求头中带上Authorization: Bearer 令牌→ 后端拦截器校验令牌合法性 → 从令牌中解析用户身份 → 查询权限 → Controller处理请求 → Service执行业务逻辑 → Mapper访问数据库 → 结果逐层返回 → 前端根据数据渲染页面。这个链路里最关键的环节是身份认证和权限校验。没有这两层接口完全裸露企业数据可以直接被别人调用拉走后果不堪设想。后端拦截器的思路值得展开说一下。这里通常是一个自定义的拦截器或者Spring Security的过滤器链拦截所有/api/**的请求。第一步判断请求头中是否存在token不存在直接返回401存在则解析token如果解析失败或者过期同样返回401。通过校验后把用户ID放入ThreadLocal这样后面任何业务方法里都可以方便地拿到“当前操作人是谁”。2.2 数据库表设计照着画你也能做OA数据库设计是OA系统的灵魂。我挑三组核心表来拆解看懂这三组表整个OA的数据模型就通了。第一组是权限模型基本是经典的RBAC基于角色的访问控制五表设计-- 用户表 CREATE TABLE sys_user ( user_id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT 加密密码, nickname varchar(50) DEFAULT NULL COMMENT 姓名, email varchar(100) DEFAULT NULL COMMENT 邮箱, phone varchar(20) DEFAULT NULL COMMENT 手机号, status tinyint DEFAULT 1 COMMENT 状态1正常 0停用, dept_id bigint DEFAULT NULL COMMENT 部门ID, create_time datetime DEFAULT NULL, PRIMARY KEY (user_id) ) ENGINEInnoDB COMMENT用户表; -- 角色表 CREATE TABLE sys_role ( role_id bigint NOT NULL AUTO_INCREMENT, role_name varchar(50) NOT NULL COMMENT 角色名称, role_key varchar(50) NOT NULL COMMENT 角色权限字符串, status tinyint DEFAULT 1, PRIMARY KEY (role_id) ) ENGINEInnoDB COMMENT角色表; -- 菜单权限表这个表最重要决定了你能看到什么页面、点哪些按钮 CREATE TABLE sys_menu ( menu_id bigint NOT NULL AUTO_INCREMENT, menu_name varchar(50) NOT NULL, parent_id bigint DEFAULT 0 COMMENT 父菜单ID, path varchar(200) DEFAULT NULL COMMENT 路由地址, component varchar(200) DEFAULT NULL COMMENT 组件路径, perms varchar(100) DEFAULT NULL COMMENT 权限标识如 system:user:add, icon varchar(100) DEFAULT NULL, menu_type char(1) DEFAULT M COMMENT M目录 C菜单 F按钮, order_num int DEFAULT 0 COMMENT 排序, PRIMARY KEY (menu_id) ) ENGINEInnoDB COMMENT菜单权限表; -- 用户-角色关联表 CREATE TABLE sys_user_role ( user_id bigint NOT NULL, role_id bigint NOT NULL, PRIMARY KEY (user_id,role_id) ) ENGINEInnoDB; -- 角色-菜单关联表 CREATE TABLE sys_role_menu ( role_id bigint NOT NULL, menu_id bigint NOT NULL, PRIMARY KEY (role_id,menu_id) ) ENGINEInnoDB;这套模型最大的优势是运维友好。要给新员工开账号只需要三步建用户、分配角色、完事。角色对应菜单和按钮权限全部由超级管理员在后台页面可视化配置不需要改代码。RuoYi、Pig等知名开源项目的权限模型思路都和这个同源。第二组是部门表做公司层级关系用的。注意它设计了很多套常见的是单树结构用ancestors字段祖级列表来加速子部门查询CREATE TABLE sys_dept ( dept_id bigint NOT NULL AUTO_INCREMENT, parent_id bigint DEFAULT 0 COMMENT 父部门ID, ancestors varchar(255) DEFAULT COMMENT 祖级列表格式0,1,2, dept_name varchar(50) NOT NULL, order_num int DEFAULT 0, leader varchar(20) DEFAULT NULL COMMENT 负责人, status tinyint DEFAULT 1, PRIMARY KEY (dept_id) ) ENGINEInnoDB COMMENT部门表;部门结构看起来简单但影响面极广审批流要判断申请人属于哪个部门、归属哪位上级考勤模块要按部门出报表公告模块要支持“按部门可见”。部门挂错整个系统的数据串线事故就来了。第三组是审批业务表。以请假申请为例比较典型的设计方式是“主表 审批记录表”拆分CREATE TABLE oa_leave ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 申请人, dept_id bigint DEFAULT NULL COMMENT 申请部门, leave_type varchar(20) DEFAULT NULL COMMENT 假期类型年假、事假、病假, start_time datetime DEFAULT NULL, end_time datetime DEFAULT NULL, days decimal(5,1) DEFAULT 0 COMMENT 请假天数, reason varchar(500) DEFAULT NULL COMMENT 请假事由, status tinyint DEFAULT 0 COMMENT 状态0待审批 1审批中 2通过 3驳回, current_node varchar(50) DEFAULT NULL COMMENT 当前审批节点, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT请假申请表; CREATE TABLE oa_leave_record ( id bigint NOT NULL AUTO_INCREMENT, leave_id bigint NOT NULL COMMENT 关联请假ID, approver_id bigint NOT NULL COMMENT 审批人, approver_name varchar(50) DEFAULT NULL, opinion varchar(500) DEFAULT NULL COMMENT 审批意见, result tinyint DEFAULT NULL COMMENT 1通过 2驳回, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT请假审批记录表;主表存业务实体记录表存流转痕迹这种“业务主表 日志从表”的拆分几乎适用于所有审批场景报销、用章、采购申请都可以照葫芦画瓢。这也是我给二开项目最推荐的模式——业务数据获取方便审批痕迹又完全保留可追溯。2.3 权限控制的落地思路JWT 路由守卫 按钮级校验权限在这套系统里是分两层控制的。第一层是后端接口控制。登录成功后会得到JWT令牌其中包含了用户ID、用户名、过期时间签名防止篡改。每次客户端请求都会带这个token后端解析后查询该用户的权限集合如果请求的URL没有在权限集合内就返回“无权限”的提示。操作按钮级别的控制也在这层比如“删除用户”这个按钮前端会显示但点击后调用后端接口DELETE /system/user/1后端校验当前用户是否拥有system:user:remove这个权限标识没有就拒绝执行。第二层是前端路由控制。用户在登录时后端会返回该用户的菜单列表前端动态生成路由和菜单栏。也就是说没有权限的菜单用户根本看不到连发起请求的机会都没有。这种“前端藏入口 后端核权限”的双层策略是我觉得这套系统做得比较扎实的地方。实现细节上有一个坑值得提醒权限校验不要只做在拦截器里。拦截器只拦截路由但Controller内部的某些逻辑比如查询别人提交的申请单同样可能有越权风险。严谨做法是在Service层再加一层数据级权限控制比如非管理员只能查询和操作自己发起的申请单。这套源码里如果某些模块没有做数据隔离你在二次开发时一定要补上。3. 环境准备与源码运行实录3.1 基础环境版本清单跑了无数次环境我把这套源码需要的基础软件版本整理成了一个清单照着配基本上不踩坑软件推荐版本说明JDK1.8 或 11SpringBoot2.x的适配区间Maven3.6依赖管理必装MySQL8.0.x注意8.0的认证协议问题Node.js16.x 或 18.xVue3 Vite的构建要求前端包管理器npm 或 pnpm建议用pnpm依赖安装速度更快开发工具IntelliJ IDEA VSCode前后端分开或用IDEA也是可以的有一个非常常见的环境问题MySQL 8.0默认的认证插件是 caching_sha2_password而一些旧版JDBC驱动不认识这个插件会导致连接报错。解决办法有两种要么在pom里用最新的mysql-connector-j版本8.0.28以上要么创建用户时指定加密规则mysql_native_password。我个人推荐直接换新版驱动更干净。3.2 初始化数据库不光是导入SQL这么简单源码的doc或sql目录下通常有一个oa.sql文件里面是建库、建表、插入初始数据的所有语句。导入步骤很简单mysql -u root -p -h localhost -P 3306 oa.sql但有两个细节很多人会忽略。第一一定要检查SQL脚本开头有没有CREATE DATABASE oa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这行没有的话就得先手动建一个utf8mb4的库。第二SQL文件里的初始管理员密码通常是一段MD5加密后的值常见的是admin123经过MD5加密存储。登录时如果试不出默认密码直接去SQL文件里搜索admin关键词看注释就能找到明文密码提示。再强调一点导入完成后要确认数据库版本字符集。如果库是latin1建的会导致中文乱码或者“Incorrect string value”报错。导入前执行一句ALTER DATABASE oa CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;3.3 后端启动改配置、编依赖、跑起来用IDEA打开backend目录等Maven把依赖下载完。重点修改application.yml里的Datasource配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/oa?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password这里面的serverTimezoneAsia/Shanghai一定不能省。MySQL 8.0版本如果不带这个参数连接时经常会碰到时间偏差8小时或者直接报时间服务相关的错误。另外如果项目集成了Redis部分模块做缓存会用到也把Redis启动并把host、password配好。启动类一般为OaApplication右键选择运行后观察控制台日志看到Started OaApplication in x.xxx seconds这句话就说明后端起来了。默认服务端口看配置项server.port常见是8080。3.4 前端启动Node环境与跨域问题进入frontend目录执行npm install这一步经常出幺蛾子。如果你是Node 17版本且用了旧版某些依赖经常看到Error: error:0308010C:digital envelope routines::unsupported这个错误——这是OpenSSL的问题。临时解决办法有两种最省事的是在启动命令前加环境变量NODE_OPTIONS--openssl-legacy-provider但不如直接换成Node 16或18省心。依赖装完后看根目录下有没有.env.development文件里面配置了开发环境的代理指向VITE_APP_BASE_URL /api而 Vite 的vite.config.js里通常已经配置了代理转发server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端跑在3000端口请求/api/login时会被转发到8080后端绕开了跨域限制。启动命令执行npm run dev浏览器打开http://localhost:3000输入管理员账号密码如果能看到菜单渲染出来说明前后端已经连通。到这里这套OA系统就跑起来了。4. 二次开发指南如何新增一个“用章申请”模块跑通源码只是第一步能在这个框架上限时交付新模块才算真正拿下了OA项目的开发能力。下面我用“用章申请”这个业务场景完整演示一次新增模块的全链路操作。选它的原因是流程有审批环节、有明细字段、有状态流转代表性很强。4.1 数据层建表和实体类业务分析很简单申请用章时填写用章类型公章、合同章、财务章、用途说明、使用时间、预计归还时间。审批需要经过部门负责人、印章管理人两级。建表SQLCREATE TABLE oa_seal ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 申请人ID, dept_id bigint DEFAULT NULL, seal_type varchar(50) DEFAULT NULL COMMENT 印章类型, usage_reason varchar(500) DEFAULT NULL COMMENT 用章事由, use_time datetime DEFAULT NULL COMMENT 用章时间, return_time datetime DEFAULT NULL COMMENT 预计归还时间, status tinyint DEFAULT 0 COMMENT 0待审批 1审批中 2通过 3驳回, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT用章申请表;建表完成之后直接用MyBatis-Plus的代码生成器可以节省大量体力。如果不想手动配生成器最快的办法是仿照已有的oa_leave模块把Leave相关的实体、Mapper、Service、Controller复制一份改名称改字段五分钟搞定。很多人代码生成器配置一个早上都排不好其实直接复制已有Mongoose模块改改是OA项目里性价比最高的做法。实体类举例Data EqualsAndHashCode(callSuper true) public class Seal extends BaseEntity { TableId(type IdType.AUTO) private Long id; private Long userId; private Long deptId; private String sealType; private String usageReason; private LocalDateTime useTime; private LocalDateTime returnTime; private Integer status; }BaseEntity已经封装了createTime这些通用字段继承它就不用每张表都重复写了。4.2 后端Service层的审批状态流转Service层是核心尤其是状态流转逻辑。用章申请的状态机和请假完全一致只是审批节点数量不同。审批接口的逻辑可以参考下面这个案例Service public class SealServiceImpl extends ServiceImplSealMapper, Seal implements SealService { Override public boolean submit(Long id) { Seal seal this.getById(id); seal.setStatus(SealStatus.PENDING_APPROVAL.getCode()); // 状态设为待审批 return this.updateById(seal); } Override public boolean approve(ApprovalRequest request) { Seal seal this.getById(request.getSealId()); // 第一级审批通过后进入第二级审批 if (seal.getStatus() SealStatus.PENDING_APPROVAL.getCode()) { seal.setStatus(SealStatus.APPROVING.getCode()); // 记录审批日志插入oa_seal_record return this.updateById(seal); } // 第二级审批通过流程结束 if (seal.getStatus() SealStatus.APPROVING.getCode() Boolean.TRUE.equals(request.getApproved())) { seal.setStatus(SealStatus.APPROVED.getCode()); return this.updateById(seal); } // 任一级驳回应直接置为驳回过 if (Boolean.FALSE.equals(request.getApproved())) { seal.setStatus(SealStatus.REJECTED.getCode()); return this.updateById(seal); } return false; } }这里有一个非常实际的经验审批的状态字段不要用有业务含义的字符串直接用数字枚举值再通过后端枚举类映射展示文本。用字符串“approving”看着直观但改了流程名就要跟着改数据后面数据清洗会让你痛不欲生。数字枚举配合Java枚举类OCR既可以做界面显示也可以做状态判断扩展性完全不同。Controller层按 Restful 风格暴露接口RestController RequestMapping(/seal) public class SealController { GetMapping(/list) public TableDataInfo list(SealQuery query) { // 分页查询申请列表 } PostMapping public AjaxResult add(RequestBody Seal seal) { // 新增申请 } PutMapping(/approve) public AjaxResult approve(RequestBody ApprovalRequest request) { // 审批通过/驳回 } }注意这里列表查询有一个权限问题普通员工应该只能看到自己的申请部门经理能看到本部门的申请管理员能看到全部。严格来说需要写SQL逻辑lambdaQueryWrapper .eq(Seal::getUserId, currentUserId) // 员工只看自己 .or(Eq(Seal::getDeptId, currentDeptId)) // 部门负责人看本部门权限控制贯穿OA的各个模块新增业务时最容易漏的就是数据权限几乎可以称得上OA开发的头号安全漏洞。4.3 前端三种件套十分钟出页面前端新增一个模块核心就是三件事API封装、路由挂接、页面组件。API封装写在src/api/seal.js里import request from /utils/request export function listSeal(query) { return request({ url: /seal/list, method: get, params: query }) } export function addSeal(data) { return request({ url: /seal, method: post, data: data }) } export function approveSeal(data) { return request({ url: /seal/approve, method: put, data: data }) }页面组件放在src/views/seal/index.vue用表格展示列表弹窗做新增和审批。参考已有的请假模块页面把table、form、dialog结构和组件名替换成Seal相关即可同时把拼音相关的变量统一换名避免前端组件里出现跨模块的名称混淆。路由挂接是关键普通前端项目可能直接在手写路由表里加一段就行但OA这类权限系统菜单是动态路由必须在sys_menu表里插入菜单记录否则用户登录后看不到这个模块-- 插入“用章申请”菜单假设父菜单是审批中心menu_id100 INSERT INTO sys_menu(menu_id, menu_name, parent_id, path, component, perms, menu_type, order_num) VALUES(2000, 用章申请, 100, seal, seal/index, oa:seal:list, C, 1); -- 给管理员角色role_id1分配菜单权限 INSERT INTO sys_role_menu(role_id, menu_id) VALUES(1, 2000);插入菜单后前端必须重新登录一次。因为登录时后端返回的菜单列表是查库得到的不重新登录系统里还是旧的菜单集合。这一条很容易被二开的同学忽略前前后后卡半小时才反应过来。页面组件里还要注意按钮的权限控制。增删改查按钮不能无条件显示——维护和审核权限可能是分开的需要根据用户是否拥有某个权限标识来决定是否渲染。通常用自定义指令v-hasPermi[oa:seal:add]来控制这一点建议后续二开时严格执行直接做硬编码按钮写死权限会给你留一个大坑。5. 常见问题与排查技巧实录5.1 启动阶段的经典报错问题一数据库连接报 Public Key Retrieval is not allowed这是因为MySQL 8.0连接时默认不允许客户端自动获取RSA公钥。解决方式简单直接在JDBC URL末尾加上allowPublicKeyRetrievaltrueurl: jdbc:mysql://localhost:3306/oa?allowPublicKeyRetrievaltrueuseSSLfalse问题二启动提示Address already in use后端8080端口被占用检查是否有其他Java进程在跑lsof -i :8080如果确实没有其他程序占用而端口却一直提示被绑定那就直接改server.port成一个不冲突的端口同时把前端代理的 target 改成相同端口。很多同学改完前端忘记改代理导致联调时接口全部404务必两个地方同步。问题三前端npm install卡住国内直连npm官方源速度不稳定替换成淘宝镜像源是常规操作npm config set registry https://registry.npmmirror.com如果遇到某个包后期下载失败锁定node_modules里这个包自己去源头排查最常见是版本不匹配或过大超过了存储限制。5.2 运行期间的业务数据问题问题一页面乱码前端页面数据中文乱码几乎都是源文件编码不对。IDE打开项目出现乱码通常是编码选择GBK、UTF-8不匹配。解决方法是统一将IDEA右下角文件编码切换成UTF-8并设置项目全局编码为UTF-8。后端返回乱码则可能是响应头没加charset在controller上加一句produces application/json;charsetutf-8即可。问题二审批流程无法流转如果审批按钮点完了状态列还是“待审批”最直接的原因是Service层判断状态条件没满足。比如我上面示例代码里第一级审批通过后只是把状态改成“审批中”但如果你少了一个current_node字段的更新前端判断就永远停留在当前节点。排这类问题时建议先在数据库里手动把某条数据的状态改成特定值然后用Postman调接口验证流转逻辑确认后端逻辑没问题再回前端找问题。5.3 联调环节的跨域与Token问题问题一开发环境下请求跨域如果使用了两台不同端口的服务请求总是被浏览器拦截先检查配置代理是否正确。Vite代理最常见的失误是changeOrigin没设置成true导致请求头中的Host还是前端地址部分严格的后端会产生混淆。这里值班经验代理配置干净利落开发环境一般不会出现跨域拦截。问题二token失效后页面跳转登录成功后token存在本地存储里有效期一到接口返回401前端请求封装通常会在所有请求的拦截器里做统一处理清除本地token跳转回登录页。出现“页面白屏但控制台没报错”的情况优先检查拦截器中的response interceptor是否正确捕获了401状态并做了路由跳转处理。5.4 排查思路的心法说了这么多具体报错最后分享一个我自己的定位思路。看到一个Bug先确定属于哪一层前端渲染层、接口联调层、后端逻辑层、数据库存取层。按这个顺序从上往下逐层排查不要一上来就翻后端代码。多数前后端分离项目的问题其实卡在“前端认为后端应该返回什么”和“后端实际上返回了什么”两者的信息差上。先用浏览器开发者工具看接口返回的JSON再决定改哪端效率比两头乱猜高得多。这一点大概是我做OA这类管理系统最想强调的经验。6. 部署上线从开发机到生产环境6.1 前后端分离打包开发联调跑通只是第一步真正交付给甲方得打生产包。后端打包执行cd backend mvn clean package -Dmaven.test.skiptrue打包成功后会在target目录下生成oa-system.jar。用java -jar oa-system.jar就可以启动。生产环境的配置文件建议用外部application-prod.yml覆盖不要把数据库账号密码等敏感信息打进jar包。前端打包cd frontend npm run build打包产物在dist目录。把这个目录里的静态文件部署到Nginx服务器上同时配置反向代理server { listen 80; server_name oa.example.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }前端打包后不能直接刷新二级页面否则404原因是Vue的history路由没有后端配置兜底。try_files $uri $uri/ /index.html;这行就是解决这个问题的关键。部署时忘了这行页面一刷新就白屏。6.2 上线前的上线检查清单基于过去交付经验我列一个清单每一项都对应真实踩过的坑数据库备份是否做了一次完整导出备份文件是否能正常还原生产环境的application-prod.yml是否关闭了Swagger接口文档否则接口全部暴露给外部是否修改了默认的管理员密码并停用了初始测试用户账号文件上传目录头像、附件是否放到了生产环境独立目录并完成了目录权限设置服务是否用systemd或守护进程方式启动而不是直接nohup这样可以实现开机自启和崩溃自动拉起生产数据库和缓存Redis密码是否改用高强度密码是否对公网入口启用了HTTPS证书企业OA里有大量员工真实数据明文HTTP传输风险太大6.3 数据迁移与服务器平滑升级如果是从旧版OA或者其他系统迁移过来建议分两步走第一步先把基础数据部门、岗位、用户迁移完毕经过管理员确认无误后再迁移业务数据第二步业务数据迁移后一定做一次全量账目核对。比如审批记录条数要和旧系统对得上缺一条就要找到原因而不是直接把数据导过去就算完成了。真到上线当天再来补救就非常被动了。还有个服务器扩容思路在部署架构上可以先用Nginx做一层简单的主备配置后端服务挂在两台节点上一台挂掉另一台接管。对用户量不大但要求稳定的OA系统来说这个方案比直接上K8s要省很多运维精力也更容易长期维持。7. 关于这套源码我最想说的几句这类OA系统很多人第一个念头是“老旧项目”“练手水平”。但实际上企业办公这个领域新技术从来不是决定因素。你去看那些真正在企业里跑了五年十年的系统核心模块一直还是那些谁负责审批、谁来盖章、公告发给了谁、这个月的考勤人工核算要几个人天。技术永远在变业务模型很稳定。我接触过很多同学拿到这套源码第一件事就问“能不能换掉MyBatis-Plus用JPA”或者“能不能把Vue3改成React”。我的建议是先别急着换。你还没有把现有代码吃透之前替换技术栈只是给自己制造工作量。真正值得投入的时间是读懂那张部门树、弄清楚审批记录表怎么写、搞清楚权限是从菜单表到接口的映射关系。把这几个底层逻辑弄明白你换任何技术栈它都仍然是一套合格的OA。最后分享一个我自己的学习路径拿到源码之后不要从头到尾看先跑起来然后用管理员身份把每个菜单都点一遍记录哪些功能是真实可用的哪些是半成品或者模板页。接着挑一个你最熟悉的业务场景比如请假从数据库到页面完整读一遍代码画出它从“提交”到“审批完成”的流程图。做完这两个动作之后你对这套系统的理解就已经超过大多数只看了README的人了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑