简介:这套OA系统源码提供完整的企业级解决方案,核心亮点在于用户可自行设计审批流程,适合需要根据内部管理需求定制审批逻辑的开发者或企业团队。资源总计两千个文件,以C#代码和ASP.NET页面为主,辅以图片素材、JavaScript脚本及CSS样式等前端资源,压缩包大小43.54MB,目录结构涵盖移动端、Web端、数据库工具库、业务逻辑层及公共模块。已有6929人学习下载,源码从数据库交互到业务规则均清晰分层,便于开发者理解整个系统的架构设计,尤其能掌握审批流程从表单提交、规则配置到状态流转的实现方式,并可直接扩展文档管理、任务分配等日常办公功能。对于希望快速搭建或二次开发办公自动化系统的技术人员,这是一份具有实践参考价值的完整源码包。
1. 选OA源码之前,先问一句:审批流程能不能自己画?
市面上能跑的OA系统源码不少,但多数所谓的“开源”OA,流程是写死在代码里的。想加一个“部门经理先审、财务再核、总经理终审”的环节,得改Java类、改数据库表结构、重新编译部署,审批流变成了一场开发灾难。这套源码的核心卖点不在界面有多漂亮,而在“可自己设计审批流程”——流程节点、审批人、条件分支都通过可视化设计器配置,存成JSON,审批时动态解析,改流程不用动代码。它适合手里有明确审批场景、不想被固定流程锁死、又愿意花半天时间自己部署一套系统的从业者。下面我会把源码结构、启动步骤、流程设计器原理、生产环境部署和常见坑一次讲透。
2. 源码拆开看:从表结构到BPM引擎的代码地图
2.1 技术栈与选型理由
这类自带流程设计器的OA系统,主流技术栈是Java系:Spring Boot做后端骨架,MyBatis操作数据库,MySQL存业务和流程数据,前端用Vue + Element UI做管理界面,流程设计器部分用原生JS或vis.js实现拖拽。选这套组合不是偶然,Spring Boot的自动配置能省掉大量XML配置,MyBatis写复杂审批查询时比JPA更直接,Vue的组件化开发正好匹配流程设计器这种“节点+连线”的高交互场景。
我拆过的几套同类源码里,数据库一般分两类表:一类是组织权限表(user、role、department、menu),一类是流程引擎表(flow_definition、flow_node、flow_instance、flow_task)。前者管“谁能登录、能看什么菜单”,后者管“审批流怎么定义、跑到哪一步了”。理解这两类表的界限很重要,二次开发时最怕在组织表里塞流程字段,或者在流程表里硬关联部门结构。
2.2 核心模块与代码地图
打开源码目录,重点关注这几个包:
com.xxx.oa ├── controller # 接口层:审批发起、审批通过、流程设计器保存 ├── service # 业务层:流程解析、节点跳转、任务分配 ├── mapper # MyBatis数据层:流程定义、实例、任务查询 ├── model # 实体类:FlowDefinition、FlowNode、FlowTask ├── flow # 流程引擎核心:解析JSON、执行节点流转 └── config # 全局配置:拦截器、权限校验、数据源flow这个包是整个系统的核心,通常包含三块逻辑:流程定义解析器(读JSON,把节点和连线转成对象图)、节点执行器(按类型分发到审批节点、条件节点、抄送节点)、任务分配器(根据审批人配置决定下一任审批人是谁)。如果你下载的源码没有独立的flow包,那多半是把流程逻辑塞在service层里,这种结构后期改起来会很吃力。
2.3 流程引擎的表结构设计
流程相关表是最值得先读的,因为设计器画的每一张图最终都落到这几张表里:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| flow_definition | 流程定义表 | id、flow_name、flow_json、status |
| flow_node | 流程节点表 | id、definition_id、node_type、approver_type、condition_expr |
| flow_instance | 流程实例表 | id、definition_id、business_key、current_node、status |
| flow_task | 任务表 | id、instance_id、node_id、assignee、approve_result、comment |
flow_definition.flow_json存的是设计器保存的完整JSON,包括所有节点的坐标、属性、连线关系;flow_node和flow_task才是审批运行时真正查询的表。设计器点保存时,前端把画布JSON提交到/flow/definition/save,后端解析后拆分写入flow_node,这张表是流程引擎的判断依据。
3. 把源码跑起来:建库、改参数、登录第一笔审批
3.1 准备环境与数据库初始化
先确认本机环境:JDK 1.8+、Maven 3.6+、MySQL 5.7+。这套源码依赖不算新,JDK 8完全够,不建议直接上JDK 17,有些老版本依赖在JDK 17下会报模块访问错误。
# 创建数据库,注意字符集 CREATE DATABASE oa_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入数据库脚本(一般在 sql/ 目录下) mysql -uroot -p oa_system < sql/oa_init.sql;导入脚本常见有两个文件:oa_init.sql是表结构,oa_data.sql是初始数据。注意不要只导前者不导后者,否则登录时账号都不存在。我习惯先看一遍oa_data.sql里的admin账号和初始菜单,确认密码是明文还是MD5加密——如果是MD5,拿初始化账号登录后第一件事就是改密码。
3.2 修改配置并启动服务
改配置是第一个容易翻车的地方,核心配置都在application.yml:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/oa_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.xxx.oa.model提示:
serverTimezone必须显式声明,很多OA系统部署后报时区错误或日期差8小时,就是少了这一项。MySQL 8.0以下用com.mysql.jdbc.Driver,8.0及以上用com.mysql.cj.jdbc.Driver,驱动类写错会在启动时直接抛ClassNotFoundException。
# 启动后端服务 mvn spring-boot:run启动日志出现Started Application in xx seconds基本就成了。前端部分如果是Vue项目,进入frontend目录执行npm install && npm run dev,浏览器访问http://localhost:8080。如果前端是纯静态页面,直接放到src/main/resources/static下访问即可,省去Node环境。
3.3 登录后先验证两条主链路
登录成功后不要急着到处乱点,先走两条链路:一条是“新建审批→选择流程→填写表单→提交”,确认流程实例能创建;另一条是“审批中心→通过/驳回→查看流程走向”,确认节点能流转。这两条链路通了,说明流程引擎整体可用。如果第一条就失败,优先看flow_instance表有没有插入记录;第二条失败,看flow_task表的assignee字段是否正确分配给了当前登录人。
4. 流程设计器核心原理:JSON驱动的审批流是怎么落地的
4.1 流程定义的数据结构
流程设计器是这套源码最值得研究的部分,它的核心数据结构是把画布上的节点和连线序列化成一个JSON对象:
{ "flowName": "请假审批流程", "nodes": [ { "id": "node_start", "type": "start", "name": "开始", "next": "node_1" }, { "id": "node_1", "type": "approval", "name": "部门经理审批", "approverType": "role", "approverValue": "dept_manager", "next": "node_2" }, { "id": "node_2", "type": "condition", "name": "请假天数判断", "conditionExpr": "${days} > 3", "next": "node_3", "otherwise": "node_end" }, { "id": "node_3", "type": "approval", "name": "总经理审批", "approverType": "user", "approverValue": "10001", "next": "node_end" }, { "id": "node_end", "type": "end", "name": "结束" } ] }这个JSON的核心在设计意图:nodes数组定义了所有节点,type决定节点类型,next指向下一个节点ID,conditionExpr是条件分支的表达式。保存流程时,后端拿到这个JSON后做两件事——把整体JSON写入flow_definition.flow_json做备份,同时解析每个节点写入flow_node表供运行时查询。
4.2 审批人、条件分支与会签的实现方式
审批人配置是这个设计器是否好用的分水岭。常见的有三种:指定用户(写死user_id)、指定角色(运行按角色查人)、按发起人上级(动态找发起人的部门负责人)。源码里一般用approverType字段区分:
// 审批人解析伪代码 public List<String> resolveApprovers(FlowNode node, FlowInstance instance) { String type = node.getApproverType(); if ("user".equals(type)) { return Arrays.asList(node.getApproverValue()); } if ("role".equals(type)) { return userMapper.findByRoleId(node.getApproverValue()); } if ("leader".equals(type)) { User initiator = userMapper.findById(instance.getInitiatorId()); return Arrays.asList(initiator.getLeaderId()); } // 其他自定义类型抛异常,方便设计器做校验 }条件分支的表达式解析是关键。这套源码实现时一般在节点流转器里处理条件节点:
// 节点流转伪代码 public String getNextNodeId(FlowNode node, FlowInstance instance, Map<String, Object> formData) { if ("condition".equals(node.getType())) { ConditionEvaluator evaluator = new ConditionEvaluator(); // 把表单字段填充到上下文,再执行表达式 boolean result = evaluator.evaluate(node.getConditionExpr(), formData); return result ? node.getNext() : node.getOtherwise(); } return node.getNext(); }参数说明:formData是审批表单的键值对集合,${days}这类占位符从formData里取值。写条件表达式时,字段名必须和表单字段的name严格一致,很多人就是在这里翻车的。
会签(所有审批人必须全部同意)和或签(任一人同意则通过)一般通过flow_task表增加一个task_type字段区别。会签实现逻辑是:审批人列表全部生成task记录,每完成一个task判断是否还有未处理的任务,全部完成后节点才算结束;或签则是在任一人通过后直接置空其他task。
4.3 表单设计与流程绑定
表单这块源码通常支持“自定义表单”,实现方式是在form_template表存表单的JSON模板,包括字段名、类型、是否必填。发起审批时,前端读取模板动态渲染表单,提交时把表单数据作为JSON存进flow_instance.form_data字段。
绑定流程的逻辑一般在流程设计器里加一个“关联表单”选项:建流程时选择一个已配置的表单模板,建好后的映射关系存在flow_definition.form_template_id字段里。发起审批时前端根据这个ID加载对应表单。如果下载的源码表单是硬编码的表单页面,那自定义能力会弱一截,但审批流程本身不受影响。
5. 避坑指南:部署和流程自定义里最常见的六个坑
5.1 建好流程点击发布后,发起审批时看不到该流程
现象:流程设计器里明明保存成功了,但发起审批的流程列表里没有。
原因:状态字段没生效。设计器保存时通常有“草稿”和“发布”两种状态,很多源码保存时默认草稿,必须在列表页点发布。如果发布后仍看不到,检查flow_definition.status字段,确认SQL查询是否带了WHERE status = 1。
解决:把流程改为发布状态,若逻辑里没有发布动作,直接手工把status改为1,或者找设计器里的发布按钮。
5.2 审批提交后任务表里没有任何记录
现象:发起人点提交,系统提示成功,但审批人的待办列表是空的,flow_task表无数据。
原因:节点流转的“分配审批人”环节出了问题,最常见的是审批人配置类型和值不匹配——比如配置的角色ID在数据库不存在,或者当前发起人在用户表里没有leader_id。
解决:先查flow_instance,确认流程实例创建成功;再查flow_node,看当前节点归属;最后用4.2中的解析逻辑手动跑一遍resolveApprovers,定位是角色查空还是上级查空。这个坑排查得最多,建议在service层加日志。
5.3 条件分支永远走默认路线
现象:请假天数超过3天,流程还是直接跳到结束,不走总经理审批。
原因:条件表达式写错,最常见的是表单字段名对不上。设计器里字段显示名为“请假天数”,但表单name是leave_days,表达式写了${days} > 3,取值时找不到days。
解决:打开发起审批页面的浏览器F12,查看表单提交的JSON字段名,确保表达式变量名和表单name一致。如果条件节点支持多个分支,检查是否有冲突的otherwise默认出口。
5.4 审批通过后下一节点不流转
现象:当前节点审批通过,但流程不进入下一节点,实例卡在当前节点状态。
原因:next指向的节点ID不存在,或指向了已删除的节点。这个在流程设计器删除中间节点、只改连线时容易出现。
解决:查看flow_node表当前节点记录的next字段,确认目标节点存在。每条next建议在设计器保存时做一次校验,避免手误导致流程死循环。
5.5 部署到Linux后中文乱码
现象:在Windows上开发正常,部署到服务器后审批表单和流程名称全部变成问号。
原因:数据库连接串没有指定characterEncoding,或容器默认编码非UTF-8。MySQL的连接串里如果只有useSSL,没写characterEncoding,中文大概率乱码。
解决:url参数补全characterEncoding=utf8,并确认数据库本身字符集是utf8mb4。启动脚本加上-Dfile.encoding=UTF-8,同时检查JVM默认编码,这两个位置都改掉才能彻底解决。
5.6 页面能登录但菜单不显示
现象:admin账号能登录,但首页菜单空白,接口返回401。
原因:权限拦截器把菜单接口拦掉了。很多OA的权限拦截器和shiro或spring security绑定,admin账号的token过期或者角色缓存没刷新,导致认证失败。
解决:清理token缓存,确认admin账号绑定了最高角色,检查权限表里菜单和角色的关联数据是否完整。改过数据库里的角色权限后,重启服务或清Redis缓存再试。
6. 进阶玩法:会签、加签与审批超时催办
6.1 把单一审批改成会签
很多OA系统的节点默认是“一个人通过就OK”,但真实场景里经常需要“部门负责人、分管副总都要同意”。改造思路是在流程节点表增加approval_mode字段,值为single或countersign:
public void completeTask(FlowTask task, String approveResult) { if ("countersign".equals(task.getApprovalMode())) { // 更新当前任务为已审核 flowTaskMapper.updateStatus(task.getId(), approveResult); // 查询同节点下的其他未处理任务 int pending = flowTaskMapper.countPendingByNode(task.getInstanceId(), task.getNodeId()); if (pending > 0) { return; // 还没全部审批完,节点不流转 } // 全部完成,节点流转到下一节点 flowEngine.advanceToNext(task.getInstanceId()); } }这里的关键是countPendingByNode的查询条件:不仅要过滤nodeId,还要过滤task状态是待办的数据,否则已处理的和未处理的混在一起,会触发提前流转。加了这个字段之后,流程设计器里需要预留一个“会签/或签”的下拉选项。
6.2 审批转办与加签
转办是当前审批人把任务移交给别人,加签是当前节点多拉一个人参与审批。实现上需要拆两步:转办本质是修改flow_task.assignee字段,同时记录操作日志;加签本质是新增一条task记录,并把原task挂在同一个节点下。这两个功能适合放在待办列表的“更多操作”菜单里,和审批按钮并列。
6.3 审批超时自动催办
生产环境里最常被吐槽的就是“审批卡在某个环节三天没人处理”。可以用一个定时任务扫描超时任务:
@Component public class TimeoutRemindTask { @Scheduled(cron = "0 0 9 * * ?") public void remind() { List<FlowTask> timeoutTasks = flowTaskMapper.findTimeoutTasks(24); for (FlowTask task : timeoutTasks) { // 发送站内信或短信提醒 notifyService.sendRemind(task.getAssignee(), task); } } }参数说明:findTimeoutTasks(24)表示超过24小时未处理的task,这个阈值应该做成系统参数,放在sys_config表里,不要写死在代码里。定时任务的时间点选在工作日早上9点,避免半夜短信轰炸。
从那以后我每次部署OA系统,都会先走一遍完整的“设计流程→发起审批→条件分支→会签通过→驳回重走”链路,确认流程引擎的每个环节都在预期内,再让业务部门上手用。这套源码本身不难,难点全在细节,希望这篇文章里的参数和踩坑记录能帮到你。
本文还有配套的精品资源,点击获取