# workflow-engine **Repository Path**: dengmin/workflow-engine ## Basic Information - **Project Name**: workflow-engine - **Description**: 自研轻量工作流引擎(纯 Java, 零依赖) 不依赖任何开源工作流框架(如 Flowable/Activiti/Camunda),仅使用 JDK 实现的工作流引擎。 - **Primary Language**: Java - **License**: LGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-04 - **Last Updated**: 2026-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 自研轻量工作流引擎(纯 Java, 零依赖) 不依赖任何开源工作流框架(如 Flowable/Activiti/Camunda),仅使用 JDK 实现的工作流引擎。 ## 核心能力 | 能力 | 说明 | | --- | --- | | 多人会签 | 三种策略:全签 `ALL`(全部同意才通过, 一人拒绝即驳回)、或签 `ANY`(一人同意即通过, 全部拒绝才驳回)、比例签 `RATIO`(同意人数达到阈值即通过) | | 转办 | 待办任务转给他人处理;原任务标记 `TRANSFERRED`,新任务保留 `originalAssignee` 可追溯转办链路 | | 撤销流程 | 仅发起人在流程运行中可撤销,撤销后所有待办自动取消 | | 撤销审批 | 审批人撤回本人已完成的审批;若节点仍在进行(会签中)直接恢复任务,若已流转到下游则校验下游任务全部未处理后回溯作废,并将节点恢复为活动状态 | | 自定义参数 | 三个层次:① `VariableDef` 流程变量定义(启动时校验必填与类型)② `FormField` 节点表单字段(审批时填写、校验并合并进流程变量)③ `FlowNode.customParam` 任意节点自定义参数 | | 条件流转 | 排他网关 + 带条件表达式的连线,如 `amount > 5000` | | 历史审计 | 全部动作记录 `HistoryRecord`,支持按实例查询完整流转历史 | | 双存储后端 | 存储层抽象为 `WorkflowStorage` 接口: 内存存储(`MemoryStorage`,支持 JDK 序列化快照)与 MySQL 存储(`MySQLStorage`,纯 JDBC 实现,自动建表) | | XML 定义 | 通过 XML 文件/字符串定义流程,基于 JDK 内置 DOM 解析(已防 XXE),见 `XmlProcessParser` | ## 目录结构 ``` src/com/workflow/ ├── engine/ 引擎核心 │ ├── WorkflowEngine.java 引擎入口: 部署/发起/审批/拒绝/转办/撤销/查询 │ └── WorkflowException.java 业务异常 ├── model/definition/ 流程定义模型 │ ├── ProcessDefinition.java 流程定义(节点+连线+变量定义) │ ├── FlowNode.java 节点(类型/处理人/会签策略/表单/自定义参数) │ ├── SequenceFlow.java 连线(可带条件表达式) │ ├── NodeType.java START/USER_TASK/APPROVAL/EXCLUSIVE_GATEWAY/END │ ├── ApprovalStrategy.java NONE/ALL/ANY/RATIO │ ├── FormField.java 表单字段定义 │ └── VariableDef.java 流程变量定义 ├── model/runtime/ 运行时模型 │ ├── ProcessInstance.java 流程实例(含变量) │ ├── TaskInstance.java 任务实例(含转办追溯) │ ├── NodeExecution.java 节点执行记录 │ ├── NodeExecutionLink.java 执行链路(审批撤销回溯用) │ ├── HistoryRecord.java 历史审计记录 │ └── ...枚举(状态/动作/取消原因) ├── expression/ │ └── ExpressionEvaluator.java 极简条件表达式求值器(自研, 无第三方依赖) ├── xml/ │ └── XmlProcessParser.java XML 流程定义解析器(JDK DOM, 防 XXE) ├── storage/ │ ├── WorkflowStorage.java 存储层接口 │ ├── MemoryStorage.java 内存存储 + JDK 序列化快照 │ └── MySQLStorage.java MySQL 存储(纯 JDBC, 自动建表/幂等 upsert) └── demo/ └── WorkflowDemo.java 8 个演示场景 ``` - `processes/leave.xml`: XML 流程定义示例 - `schema.sql`: MySQL 表结构(与 `MySQLStorage` 内置 DDL 一致, 可手工执行) - `lib/`: MySQL JDBC 驱动(mysql-connector-j, 场景 8 运行时需要) ## 运行 ```bash ./run.sh # 编译并运行演示(仅需 JDK 8+) ``` 或手动: ```bash mkdir -p out javac -encoding UTF-8 -d out $(find src -name "*.java") java -cp out com.workflow.demo.WorkflowDemo ``` ## 快速上手 ```java WorkflowEngine engine = new WorkflowEngine(); // 1. 构建并部署流程定义 ProcessDefinition def = new ProcessDefinition("expense", "报销审批流程"); def.variable(new VariableDef("amount", "报销金额", FieldType.NUMBER, true, null)); // 自定义参数: 变量定义 def.addNode(FlowNode.start("start", "发起")); def.addNode(FlowNode.approval("leader", "主管审批", "王主管") .formField(new FormField("opinion", "意见", FieldType.STRING, false)) // 自定义参数: 表单 .customParam("notify", "dingtalk")); // 自定义参数: 任意参数 def.addNode(FlowNode.approval("cs", "会签").countersignAll("财务经理", "财务总监")); // 全签会签 def.addNode(FlowNode.approval("finance", "财务审批", "财务小赵")); def.addNode(FlowNode.gateway("gw", "金额网关")); def.addNode(FlowNode.end("end", "结束")); def.addFlow("start", "leader"); def.addFlow("leader", "gw"); def.addFlow("gw", "cs", "amount > 5000"); // 条件分支 def.addFlow("gw", "finance", "amount <= 5000"); def.addFlow("cs", "end"); def.addFlow("finance", "end"); engine.deploy(def); // 2. 发起流程(变量按 VariableDef 校验) ProcessInstance ins = engine.startProcess("expense", "小李", Map.of("amount", 8000, "reason", "出差")); // 3. 处理任务 TaskInstance task = engine.getTodoTasks("王主管").get(0); engine.approve(task.getId(), "王主管", "同意", Map.of("opinion", "同意")); // 同意(可带表单) engine.reject(task.getId(), "王主管", "不同意"); // 驳回(终止流程) engine.transfer(task.getId(), "财务总监", "刘副总", "出差代审"); // 转办 // 4. 撤销 engine.withdrawApproval(taskId, "王主管", "撤回重审"); // 撤销本人审批(自动回溯下游) engine.withdrawInstance(ins.getId(), "小李", "重复提交"); // 发起人撤销流程 // 5. 查询 engine.getTodoTasks("财务经理"); // 待办 engine.getTasksOfInstance(ins.getId()); // 实例任务列表 engine.getHistoryOfInstance(ins.getId()); // 流转历史 // 6. 持久化 engine.saveSnapshot(new File("wf.dat")); WorkflowEngine restored = WorkflowEngine.loadSnapshot(new File("wf.dat")); ``` ## 撤销语义 - **撤销流程**:发起人权限;实例置 `WITHDRAWN`,所有待办置 `CANCELLED(INSTANCE_TERMINATED)`。 - **撤销审批**: - 节点仍在进行中(如会签未结束):本人任务直接恢复为待处理; - 节点已完成:沿执行链路(`NodeExecutionLink`)找到当前活动节点,若其任务全部仍为待处理,则取消后续任务与执行、作废链路、把本节点(含会签中被提前取消的任务)恢复为活动;否则拒绝撤销(如"后续节点已被处理")。 - 流程已结束(`COMPLETED/REJECTED`)时一律不可撤销。 ## 条件表达式语法 `变量名 运算符 字面量`,运算符 `> < >= <= == !=`;数字按数值比较,否则按字符串比较;字面量可用引号包裹。空条件连线作为兜底分支。 ## XML 流程定义 支持用 XML 声明式定义流程,解析后与 Java 构建的定义完全等价(同样经过部署校验)。 ```java engine.deployFromXml(new File("processes/leave.xml")); // 从文件 engine.deployFromXml(xmlString); // 从字符串 engine.deployFromXml(inputStream); // 从输入流 ``` XML 结构(完整示例见 `processes/leave.xml`): ```xml 王主管 dingtalk HR小李 陈总监 ``` 解析器说明: - 基于 JDK 内置 DOM(`javax.xml.parsers`),零第三方依赖;已禁用 DOCTYPE/外部实体,防止 XXE 注入 - 元素只取直接子节点匹配,属性缺失/取值非法时抛出带定位信息的异常(如"节点 [n] 的 type 非法: FOO") ## MySQL 存储 存储层抽象为 `WorkflowStorage` 接口,引擎通过接口读写所有状态并对每个变更点显式落盘,因此可在内存与数据库实现间自由切换。`MySQLStorage` 为纯 JDBC 实现(不使用任何 ORM/连接池框架)。 ```java // 建表会在构造时自动执行(也可手工执行 schema.sql) MySQLStorage storage = new MySQLStorage( "jdbc:mysql://127.0.0.1:3306/workflow?useSSL=false&characterEncoding=utf8", "wf", "wf_pass_123"); WorkflowEngine engine = new WorkflowEngine(storage); // 用 MySQL 跑流程 WorkflowEngine mem = new WorkflowEngine(); // 默认仍是内存存储 ``` 要点: - 表结构见 `schema.sql`:`wf_definition / wf_instance / wf_task / wf_execution / wf_link / wf_history / wf_sequence`。 - 流程定义与流程变量等复杂对象以 Java 序列化 BLOB 存储;任务/实例/执行/链路/历史以结构化列存储,并按处理人、状态、实例建了索引。 - `nextId` 通过 `wf_sequence` 序列表 + `LAST_INSERT_ID()` 原子自增,幂等写入采用 `INSERT ... ON DUPLICATE KEY UPDATE`。 - 每次操作打开短连接,简单可靠;高并发下可自行在前面套一层连接池(存储层无需改动)。 - 运行演示的场景 8 需要可用的 MySQL,连接可用环境变量 `WF_MYSQL_URL / WF_MYSQL_USER / WF_MYSQL_PASSWORD` 覆盖;`lib/` 中需提供 MySQL JDBC 驱动。 ## 线程安全与扩展 - 引擎所有公开方法加 `synchronized`,可安全用于多线程环境。 - 存储层面向 `WorkflowStorage` 接口,内存/MySQL 可互换;定义与变量模型均实现 `Serializable`,可直接序列化存储。 - 可扩展方向:并行网关、加签/委托、超时催办、驳回回退至任意历史节点、流程定义版本迁移、在存储层外再包一层连接池等。