简介这是一套面向开发者与团队管理者的完整OA办公自动化系统源码核心价值在于审批流程可自行设计与定制适用于企业内部请假、报销等业务审批场景也可作为学习OA架构与业务逻辑层实现的实战参考。压缩包共约2000个文件整体43.54MB以gif、cs、aspx、png、js、doc、dll等为主涵盖移动端与Web端界面、数据库工具类、业务逻辑层及公共模块结构上分为MobileWeb移动端、Web应用、TMOA.BLL业务逻辑、TMOA.Common公共组件与数据库文件等部分便于按模块研究。目前已有6930人学习下载说明其参考价值获得较多认可。读者可借此了解审批流程的实现方式、业务规则如何融入系统以及移动端与Web端协同的架构设计从而快速上手并完成定制化改造。1. 一套完整 OA 系统源码为什么审批流程必须自己攥在手里很多团队第一次选 OA 系统源码眼睛盯的是「功能全不全」——考勤、公告、文档、报销一应俱全装完就能跑。真上线三个月业务部门提一个需求报销单超过 5000 元要自动加签财务总监再超过 20000 元加签总经理。你打开后台一看流程引擎是写死的节点顺序在代码里硬编码改一行要重新编译整个包。这时候才明白OA 系统源码的价值不在功能数量而在审批流程能不能自己设计。这篇讲的就是这件事拿到一套完整 OA 系统源码之后怎么把审批流程从「厂商说了算」变成「自己说了算」。核心思路是把流程定义从代码里抽出来做成可配置的模型——谁审批、什么条件走哪个分支、超时怎么办、加签怎么加全部由数据驱动。适合正在选型 OA 源码的开发者、需要二次开发的企业 IT以及想自建审批中台的团队。读完你能判断一套源码的流程引擎值不值得用也能照着把最小可用的自定义审批跑起来。2. 审批流程引擎的选型自研、嵌入还是外挂2.1 三种流程引擎落地方式的取舍拿到 OA 系统源码后审批流程的实现方式基本逃不出三条路选错了后面全是返工。第一种是纯自研状态机。用一张approval_record表记录当前节点和状态代码里写if (status manager_approve)。优点是简单、可控、没有额外依赖缺点是流程一变就要改代码条件分支一多就是灾难。适合流程固定、不超过三级的场景比如只有「提交→主管→完成」的请假。第二种是嵌入成熟流程引擎。Java 生态里常见的是 Activiti、Flowable、Camunda它们用 BPMN 2.0 标准描述流程流程定义是一个 XML 文件改流程不用改代码。代价是学习曲线陡引擎本身有几十张表出问题排查起来像开黑匣子。适合流程复杂、需要会签/并行网关/子流程的场景。第三种是外挂独立审批服务。OA 系统只负责表单和展示审批逻辑通过 HTTP 调用一个独立的流程服务。好处是流程服务和业务解耦可以单独升级坏处是多了一次网络调用事务一致性要自己保证。我一般会这样判断如果标题里强调「可自己设计审批流程」那基本排除了第一种。中小团队用 Flowable 嵌入是最稳的社区活跃、文档全、和 Spring Boot 集成成熟。下面按这个方向展开。2.2 流程定义该存数据库还是存文件选好引擎后第一个要决定的是流程定义放哪。Flowable 默认支持两种部署时把 BPMN 文件打包进应用或者运行时通过 API 动态部署到数据库。打包进应用的方式流程文件在resources/processes/目录下应用启动时自动部署。优点是版本跟着代码走回滚方便缺点是改一个审批节点也要走发版流程业务等不起。动态部署的方式流程定义存在ACT_RE_PROCDEF表里通过管理界面在线编辑、发布、切换版本。这才是「自己设计审批流程」该有的样子——业务人员在后台拖拽或填表保存即生效不用重启服务。代价是要自己做一个流程设计器。常见做法是前端集成 bpmn-js后端提供部署接口。如果不想做设计器退一步用 JSON 描述流程前端用表单渲染后端解析 JSON 生成 BPMN。这样业务人员填几个字段就能定义一条流程比拖拽图更简单也更适合非技术人员。提示动态部署要处理好版本问题。同一条流程发布新版本后已经在跑的老流程实例默认仍走老版本新发起的才走新版本。这个行为由引擎控制别自己改。2.3 最小可跑的流程定义长什么样先看一个能跑起来的最小 BPMN 定义一条报销审批两级节点。把它存成expense-approval.bpmn20.xml放进resources/processes/启动应用就会自动部署。?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://example.com/oa process idexpenseApproval name报销审批 isExecutabletrue !-- 发起节点 -- startEvent idstart name提交报销/ !-- 主管审批assignee 用表达式从流程变量取 -- userTask idmanagerApprove name主管审批 flowable:assignee${managerId}/ !-- 财务审批 -- userTask idfinanceApprove name财务审批 flowable:assignee${financeId}/ endEvent idend name完成/ sequenceFlow idf1 sourceRefstart targetRefmanagerApprove/ sequenceFlow idf2 sourceRefmanagerApprove targetReffinanceApprove/ sequenceFlow idf3 sourceReffinanceApprove targetRefend/ /process /definitions这段定义里process id是流程的唯一标识发起时用它找流程。两个userTask是人工审批节点flowable:assignee用${}表达式从流程变量里取审批人 ID这样同一条流程定义能被不同部门复用审批人由发起时传入。sequenceFlow定义节点之间的连线sourceRef和targetRef对应节点 id。启动流程的 Java 代码// 注入 Flowable 的运行时服务 Autowired private RuntimeService runtimeService; public String startExpense(String userId, String managerId, String financeId) { MapString, Object vars new HashMap(); vars.put(managerId, managerId); // 主管审批人 vars.put(financeId, financeId); // 财务审批人 vars.put(initiator, userId); // 发起人后面查待办要用 // 按流程定义 key 启动返回流程实例 ID ProcessInstance instance runtimeService.startProcessInstanceByKey( expenseApproval, vars); return instance.getId(); }startProcessInstanceByKey的第一个参数就是 BPMN 里的process id。流程变量vars会贯穿整个流程实例节点上的表达式从这里取值。拿到instance.getId()后前端就能用它查当前待办、审批记录和流程图高亮。3. 把审批流程做成可配置条件分支与动态审批人3.1 用排他网关实现金额分级审批固定两级审批只是起点真实需求是「金额不同走不同节点」。这就要用排他网关exclusiveGateway它按条件选一条分支走。在 BPMN 里加一个网关和两条分支!-- 主管审批后判断金额 -- exclusiveGateway idamountGateway name金额判断/ sequenceFlow idtoFinance sourceRefamountGateway targetReffinanceApprove conditionExpression xsi:typetFormalExpression ![CDATA[${amount 5000}]] /conditionExpression /sequenceFlow sequenceFlow idtoDirector sourceRefamountGateway targetRefdirectorApprove conditionExpression xsi:typetFormalExpression ![CDATA[${amount 5000}]] /conditionExpression /sequenceFlowconditionExpression里写的是 UEL 表达式amount是流程变量。发起流程时把报销金额放进变量引擎就会自动判断走哪条线。注意条件要覆盖所有情况否则金额正好等于某个边界值时可能找不到出口流程卡住。这里有个血泪经验条件表达式里的变量如果为 nullFlowable 会抛异常而不是当 false 处理。所以发起前一定要校验amount不为空或者在表达式里写成${amount ! null amount 5000}。3.2 审批人从组织架构动态解析写死审批人 ID 的流程没有生命力。真实场景是「审批人是发起人所在部门的负责人」这需要把组织架构接进来。常见做法是自定义一个TaskListener在节点创建时动态设置审批人public class ManagerAssigneeListener implements TaskListener { Override public void notify(DelegateTask task) { // 从流程变量拿发起人 String initiator (String) task.getVariable(initiator); // 查组织架构找到发起人的部门负责人 String managerId orgService.findDeptLeaderByUser(initiator); if (managerId null) { throw new RuntimeException(未找到部门负责人流程无法流转); } task.setAssignee(managerId); } }在 BPMN 节点上挂这个监听器userTask idmanagerApprove name主管审批 flowable:taskListenercom.example.oa.listener.ManagerAssigneeListener/这样审批人不再依赖发起时传入而是节点创建那一刻实时查组织架构。好处是人员调动后新流程自动走新负责人坏处是每次都要查库高并发下要注意缓存。我一般会在orgService里加一层本地缓存组织架构变更时主动失效。3.3 会签、加签和超时自动处理会签是「多个审批人都同意才通过」加签是「审批过程中临时拉人进来」。这两个需求在 OA 里出现频率极高。会签用multiInstanceLoopCharacteristicsuserTask idjointApprove name会签 multiInstanceLoopCharacteristics isSequentialfalse flowable:collection${approverList} flowable:elementVariableapprover completionCondition${nrOfCompletedInstances nrOfInstances}/completionCondition /multiInstanceLoopCharacteristics /userTaskisSequentialfalse表示并行会签collection是审批人列表变量elementVariable是每个实例的审批人。completionCondition定义完成条件上面写的是全部同意才完成。如果改成${nrOfCompletedInstances / nrOfInstances 0.5}就是过半数通过。加签没有标准节点常见做法是在运行时动态给当前任务加一个IdentityLink或者用runtimeService.addParticipantUser。更稳妥的方式是加签时创建一个子流程把原审批人和加签人串起来这样审批记录清晰不会污染主流程。超时自动处理用边界定时器boundaryEvent idtimeout attachedToRefmanagerApprove cancelActivitytrue timerEventDefinition timeDurationPT48H/timeDuration /timerEventDefinition /boundaryEventPT48H表示 48 小时超时后触发边界事件可以连到自动通过或转交上级的节点。cancelActivitytrue表示超时后取消原任务避免重复审批。4. 审批流程落地时最容易翻车的五个地方4.1 流程变量类型不匹配导致表达式失效现象流程走到网关就报Unknown property used in expression或者条件判断结果和预期相反。原因流程变量存进去是 String表达式里按数字比较。比如amount存的是5000${amount 5000}在 UEL 里会尝试类型转换转换失败就抛异常或走错分支。解决发起流程前统一变量类型金额用BigDecimal或Double日期用Date别用字符串凑合。在startProcessInstanceByKey之前加一层校验类型不对直接拒绝发起。4.2 动态部署的流程版本和运行实例对不上现象后台改了流程定义新发起的单子走了新流程但老单子审批到一半突然报节点找不到。原因Flowable 默认按流程定义版本隔离老实例绑定老版本。但如果手动删了老版本的部署老实例就悬空了。解决永远不要删除已部署的流程定义只做版本升级。ACT_RE_PROCDEF表里的记录是历史凭证删了后悔药都没得吃。如果确实要停用某条流程把SUSPENSION_STATE置为 2挂起新实例不能发起老实例还能跑完。4.3 会签节点审批人列表为空导致流程卡死现象会签节点创建后没有任何待办流程停在那里不动。原因flowable:collection引用的变量是空集合或 null。并行会签在集合为空时不会创建任何实例也不会自动完成。解决在进入会签节点前加一个校验集合为空时直接跳过或抛业务异常。可以在TaskListener的create事件里判断也可以在网关分支里加条件。我一般会在流程变量里放一个approverList发起时如果为空就补一个默认审批人宁可多审也别卡死。4.4 审批人离职后待办无人处理现象某个审批人离职账号被禁用他名下的待办任务全部卡住流程无法推进。原因任务assignee指向一个已失效的用户系统没有自动转交机制。解决加一个定时任务定期扫描ACT_RU_TASK表把 assignee 已离职的任务转交给其上级或管理员。更彻底的做法是在用户离职流程里触发一个事件主动查询该用户所有待办并批量转交。这个逻辑要幂等避免重复转交。4.5 流程定义 key 拼写错误导致发起失败现象调用startProcessInstanceByKey报no processes deployed with key。原因BPMN 文件里的process id和代码里传的 key 不一致或者文件没被扫描到。解决部署后先查ACT_RE_PROCDEF表确认 key 存在。Flowable 默认扫描classpath*:/processes/**/*.bpmn20.xml文件命名和路径要对。动态部署的话检查部署接口返回的deploymentId是否成功。这个错误很低级但极高频建议在启动时加一个自检把关键流程 key 列出来核对。5. 用 JSON 驱动流程定义让业务人员自己改审批流前面讲的都是 BPMN对开发友好但业务人员看到 XML 就头大。真正想让「自己设计审批流程」落地得再包一层。我的做法是用 JSON 描述流程后端解析成 BPMN 再部署。一个简化的流程 JSON 长这样{ key: expenseApproval, name: 报销审批, nodes: [ {id: start, type: start}, {id: manager, type: approve, name: 主管审批, assignee: deptLeader, timeoutHours: 48}, {id: gateway, type: gateway, conditions: [ {expr: amount 5000, target: finance}, {expr: amount 5000, target: director} ]}, {id: finance, type: approve, name: 财务审批, assignee: financeRole}, {id: director, type: approve, name: 总监审批, assignee: directorRole}, {id: end, type: end} ] }后端解析这个 JSON生成对应的 BPMN XML再调repositoryService.createDeployment()部署。业务人员在管理界面填节点、选审批人规则、设条件保存后流程立即生效。assignee字段不存具体用户 ID存的是规则标识比如deptLeader表示部门负责人financeRole表示财务角色。运行时由监听器根据规则查组织架构解析成具体人。这样人员变动不影响流程定义。验证方法很简单部署后发起一条测试流程用不同金额跑两遍看是否走了不同分支再把审批人账号禁用看超时转交是否生效。我习惯在测试环境把每条分支都跑一遍确认没有死路。这套方案我用了两年多最大的教训是别让业务人员直接编辑 BPMN。他们不需要知道什么是排他网关只需要知道「金额大于多少走谁」。把复杂度留在后端把简单留给使用者这才是「可自己设计审批流程」的真正含义。希望帮到你。本文还有配套的精品资源点击获取