简介:这是一套面向企业信息化建设者与Java开发者的OA办公自动化系统项目源码,适合需要理解办公流程数字化、审批引擎与权限体系的中高级学习者参考。资源包共1212个文件,约17.92MB,以415个js前端脚本、186个gif界面素材、177个class与84个java后端源码为核心,辅以72个xml配置、56个jar依赖、51个html与46个jsp页面、49个css样式及19个ftl模板,完整覆盖从视图层到业务层的工程结构。内容围绕需求分析、功能模块设计、流程引擎、权限管理、系统集成、移动办公、报表分析、实施培训与后期维护等关键环节展开,可帮助读者梳理公文管理、审批流转、会议与资产管理等模块的实现思路,理解流程可配置化与分级权限控制的落地方式。目前已有385人学习下载,适合作为课程设计、毕业设计或企业OA选型的技术参照。
1. 从一堆散装审批到一套能跑的系统:OA 项目到底在解决什么
很多团队都经历过这种局面:请假走群聊、报销靠邮件、采购审批用共享表格,流程一多就乱,谁批过、卡在谁那、历史记录在哪,全靠人肉翻聊天记录。这套 OA 系统项目要解决的就是这件事——把审批流、表单、权限、通知这几块从散装状态收进一个能跑、能改、能扩展的后台系统里。它适合两类人:一类是手上正好有这套源码、想先跑起来看效果的开发者;另一类是想拿它当脚手架,改出自己公司内部审批工具的熟手。整套项目围绕「流程可配置、表单可扩展、权限可控制」三个目标搭起来,不是那种只能看不能改的演示壳子。
2. 技术选型与目录结构:先看清这套 OA 是怎么搭起来的
2.1 后端技术栈与分层思路
这套 OA 项目后端常见做法是 Spring Boot + MyBatis-Plus 的组合,权限层用 Spring Security 或 Sa-Token 兜底,流程引擎部分如果项目里带了 Activiti 或 Flowable,那审批流转就是引擎驱动,如果没有,多半是自研的状态机。数据库一般是 MySQL 8,缓存用 Redis 存登录态和流程节点缓存。
分层上基本是 controller / service / mapper / entity 四层,外加一个 workflow 或 flow 包专门放流程相关逻辑。这样分的好处是:审批逻辑不跟业务 CRUD 混在一起,后面要加新流程类型时,改的是 flow 包,不动主业务。
// 典型的 Controller 层入口,接收前端提交的审批请求 @RestController @RequestMapping("/api/leave") public class LeaveController { @Autowired private LeaveService leaveService; // 提交请假申请,返回流程实例 ID @PostMapping("/submit") public Result submit(@RequestBody LeaveForm form) { // form 里包含申请人、天数、起止时间、审批人链 String processId = leaveService.startProcess(form); return Result.ok(processId); } }这段代码里LeaveForm是前端传上来的表单对象,startProcess内部会做两件事:落库一条业务记录,同时调流程引擎启动一个实例。参数processId是后续查审批进度、催办、撤回的唯一凭证。如果你拿到的项目里没有flow包,那大概率流程状态是直接存在业务表的一个status字段里,改起来更直接,但扩展性差一些。
2.2 前端结构与页面组织
前端多数是 Vue 2 或 Vue 3 + Element UI / Element Plus,目录里会有views/oa/这样的文件夹,下面按模块分:leave、expense、purchase、notice。每个模块里通常是一个列表页 + 一个表单页 + 一个详情页。
路由配置里会看到meta: { title: '请假申请', requiresAuth: true }这种写法,requiresAuth控制是否需要登录,有些项目还会加roles字段做按钮级权限。表单页一般用el-form配合rules做校验,提交时调后端/submit接口。
// 前端提交请假申请的核心逻辑 async submitLeave() { // 先做表单校验,不通过直接返回 const valid = await this.$refs.leaveForm.validate(); if (!valid) return; // 组装审批人链,通常是上级 -> 部门负责人 -> HR const payload = { ...this.form, approvers: this.buildApproverChain() }; const res = await submitLeaveApi(payload); if (res.code === 200) { this.$message.success('提交成功,等待审批'); this.$router.push('/oa/leave/list'); } }buildApproverChain这个方法值得留意,它决定了审批顺序。有些项目是写死的,有些是从组织架构表里动态查的。如果你要改成「金额大于 5000 加一级审批」,改的就是这个方法。
2.3 目录结构速查
| 目录 | 作用 |
|---|---|
src/main/java/.../controller | 接口入口,参数校验 |
src/main/java/.../service | 业务逻辑,流程启动与回调 |
src/main/java/.../flow | 流程定义、节点处理、审批人解析 |
src/main/java/.../mapper | 数据库操作 |
src/main/resources/processes | BPMN 流程文件(如果有引擎) |
src/views/oa | 前端页面,按模块分文件夹 |
src/api/oa | 前端接口封装 |
提示:先看
flow包和processes目录,能快速判断这套 OA 是引擎驱动还是状态机驱动,这决定了你后面改流程的难度。
3. 跑起来:从数据库到登录页的完整启动链路
3.1 数据库初始化与配置修改
拿到源码后第一件事不是急着mvn spring-boot:run,而是先把数据库建好。项目里一般会带一个sql/目录,里面有oa_schema.sql和oa_data.sql,前者建表,后者塞初始数据(管理员账号、部门、角色)。
# 建库并导入 mysql -u root -p -e "CREATE DATABASE oa_db DEFAULT CHARSET utf8mb4;" mysql -u root -p oa_db < sql/oa_schema.sql mysql -u root -p oa_db < sql/oa_data.sql导入完检查两张表:sys_user里有没有 admin 账号,sys_role里有没有超级管理员角色。如果oa_data.sql里没塞,登录会一直提示密码错误,这是最常见的翻车点。
然后改application.yml:
spring: datasource: url: jdbc:mysql://localhost:3306/oa_db?useUnicode=true&characterEncoding=utf8 username: root password: 你的密码 redis: host: localhost port: 6379Redis 如果本地没装,要么装一个,要么把缓存相关配置注释掉,但登录态可能依赖 Redis,注释后要改成本地 Map 存储,不推荐。
3.2 后端启动与常见报错
后端启动命令就是标准的 Maven 或 Gradle:
# Maven 项目 mvn clean package -DskipTests java -jar target/oa-system.jar # 或者直接跑 mvn spring-boot:run启动时如果报Table 'oa_db.act_ge_property' doesn't exist,说明项目用了 Flowable 或 Activiti,但建表脚本没跑全。解决办法是找到引擎自带的建表 SQL,或者把spring.flowable.database-schema-update设成true让引擎自动建表。
另一个高频报错是端口占用,OA 项目默认 8080,如果被占,改server.port就行。
3.3 前端启动与接口代理
前端一般是:
npm install npm run serve跑起来后如果接口 404,检查vue.config.js里的proxy配置:
devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }changeOrigin: true必须加,否则跨域请求会被浏览器拦。登录页默认账号通常是admin / 123456,具体看oa_data.sql里的密码字段,有些项目存的是 BCrypt 加密后的值,不能直接改明文。
注意:如果登录后菜单是空的,多半是角色和菜单的关联表没数据,去
sys_role_menu里补几条记录。
4. 核心模块拆解:审批流、表单、权限三块怎么联动
4.1 审批流的状态流转与节点处理
审批流的核心是一个状态机。以请假为例,状态可能是:草稿 -> 待审批 -> 审批中 -> 通过 / 驳回 / 撤回。每次状态变更都要落一条审批记录,记录操作人、时间、意见。
// 审批节点处理的核心逻辑 public void approve(String taskId, String approver, String comment, boolean pass) { // 1. 查当前任务 Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if (task == null) throw new BizException("任务不存在或已处理"); // 2. 记录审批意见 taskService.addComment(taskId, task.getProcessInstanceId(), comment); // 3. 根据通过与否决定流向 Map<String, Object> vars = new HashMap<>(); vars.put("approved", pass); taskService.complete(taskId, vars); // 4. 更新业务表状态 leaveMapper.updateStatus(task.getProcessInstanceId(), pass ? "APPROVED" : "REJECTED"); }vars里的approved是流程分支的判断条件,BPMN 文件里会用${approved}决定走通过线还是驳回线。如果你要加「会签」——多个人都通过才算过,改的是 BPMN 里的multiInstance配置,不是这段 Java 代码。
4.2 表单动态渲染与字段扩展
表单这块,简单项目是每个模块写死一个 Vue 页面,复杂一点的是后端返回表单配置 JSON,前端动态渲染。后者扩展性好,加字段不用改前端代码。
// 动态表单渲染:根据后端返回的 schema 生成表单项 renderForm(schema) { return schema.map(item => { switch (item.type) { case 'input': return <el-input v-model={this.formData[item.field]} />; case 'select': return <el-select v-model={this.formData[item.field]}> {item.options.map(opt => <el-option label={opt.label} value={opt.value} />)} </el-select>; case 'date': return <el-date-picker v-model={this.formData[item.field]} />; } }); }schema里每个对象包含field、type、label、options四个关键字段。要加一个「紧急程度」下拉框,只需在数据库的表单配置表里加一条记录,前端不用动。
4.3 权限控制:菜单、按钮、数据三层
权限分三层:菜单权限决定你能看到哪些页面,按钮权限决定你能点哪些操作,数据权限决定你能看谁的数据。
菜单和按钮权限一般用角色关联,登录时后端返回权限码列表,前端用指令控制显隐:
// 自定义指令 v-permission,控制按钮显隐 Vue.directive('permission', { inserted(el, binding) { const hasPerm = store.getters.permissions.includes(binding.value); if (!hasPerm) el.parentNode.removeChild(el); } });数据权限复杂一些,常见做法是在 SQL 里拼条件,比如部门负责人只能看本部门数据。改的时候注意别把WHERE条件拼错,否则要么看不到数据,要么看到不该看的。
提示:权限码建议统一格式,比如
oa:leave:approve,别用中文或随机字符串,后面排查问题会省很多事。
5. 避坑与排查:跑这套 OA 时最容易卡住的几个地方
5.1 登录成功但接口 401
现象:登录接口返回 token,但后续请求全部 401。原因通常是前端没把 token 放进请求头,或者后端拦截器配置的路径不对。解决:检查axios拦截器里有没有config.headers.Authorization = token,以及后端WebSecurityConfig里放行的路径是否包含登录接口。
5.2 流程启动后查不到待办
现象:提交申请成功,但审批人登录后看不到待办任务。原因多半是审批人 ID 传错了,或者流程定义里的候选人变量名和代码里不一致。解决:去act_ru_task表里查ASSIGNEE_字段,看是不是空或错值,再对照 BPMN 文件里的assignee配置。
5.3 前端打包后刷新 404
现象:npm run build后部署,首页能打开,刷新子页面就 404。原因是 history 路由模式需要服务端配置 fallback。解决:Nginx 里加try_files $uri $uri/ /index.html;,或者把路由模式改成 hash。
5.4 数据库时间差 8 小时
现象:审批记录里的时间比实际时间少 8 小时。原因是 JDBC 连接没设时区。解决:连接串加serverTimezone=Asia/Shanghai,或者把 MySQL 全局时区设对。
5.5 修改流程后旧实例报错
现象:改了 BPMN 文件重新部署,之前跑了一半的流程实例报错。原因是新旧流程定义版本不兼容。解决:改流程时尽量只加节点不删节点,必须删的话先把旧实例跑完或手动终止。
6. 二次开发:把这套 OA 改成自己公司能用的样子
拿到源码后,最直接的改造点是审批人链。原项目多半是「上级 -> 部门负责人 -> HR」这种固定顺序,但实际公司里可能是「直属上级 -> 分管领导 -> 财务」,而且金额不同走的线也不同。改法是重写buildApproverChain方法,从组织架构表里动态查,而不是写死。
// 改造后的审批人链:根据金额和部门动态计算 public List<String> buildApproverChain(LeaveForm form) { List<String> chain = new ArrayList<>(); // 第一级:直属上级 chain.add(orgService.getDirectLeader(form.getUserId())); // 第二级:金额大于 5000 加分管领导 if (form.getAmount() > 5000) { chain.add(orgService.getDeptLeader(form.getDeptId())); } // 第三级:HR 兜底 chain.add(orgService.getHrUserId()); return chain; }改完这个方法后,验证方式是:提交一笔金额 3000 和一笔 6000 的申请,看审批链是否不同。如果相同,检查orgService查出来的领导 ID 是不是空。
第二个改造点是表单字段。原项目的请假表单可能只有「天数」和「事由」,你要加「请假类型」和「附件」。改法是先在数据库表单配置表里加两条记录,然后在前端renderForm的switch里加upload类型分支。
第三个改造点是通知。原项目可能只有站内信,你要加邮件或企业微信通知。做法是在流程状态变更后发一个事件,监听器里调通知服务。注意别把通知逻辑写在approve方法里,否则流程一慢通知就堵。
| 改造点 | 改动位置 | 验证方式 |
|---|---|---|
| 审批人链 | flow包下的buildApproverChain | 提交不同金额申请看链路 |
| 表单字段 | 表单配置表 + 前端renderForm | 新增字段能否渲染和提交 |
| 通知方式 | 事件监听器 + 通知服务 | 审批后能否收到邮件 |
| 权限粒度 | sys_role_menu+ 前端指令 | 不同角色登录看按钮显隐 |
我自己的习惯是:每次改完流程相关代码,先跑一遍「提交 -> 审批 -> 驳回 -> 重新提交」的完整链路,再去看数据库里act_ru_task和业务表的状态是否一致。这个习惯帮我挡掉过好几次「流程卡死但页面不报错」的玄学问题。从那以后我每次动流程配置都强制走一遍全链路,希望帮到你。
本文还有配套的精品资源,点击获取