工作流编辑与执行实战:基于Flowable与Spring Boot的流程自动化指南
在业务系统开发中,你是否遇到过这样的场景:一个审批流程需要经过多个部门,一个数据处理任务需要按顺序执行多个步骤,或者一个复杂的业务逻辑需要协调多个服务?手动串联这些步骤不仅效率低下,而且容易出错,难以维护。这时,工作流技术就成为了解决问题的利器。本文将围绕工作流的编辑与执行,为你提供一套从概念理解到实战落地的完整指南。无论你是刚接触工作流的新手,还是希望优化现有流程的开发者,都能从中找到清晰的路径和可复用的代码。
1. 工作流核心概念与价值
在深入编辑与执行之前,我们首先要理解工作流是什么,以及它能为我们带来什么。
1.1 什么是工作流?
工作流(Workflow)是对业务流程的一种形式化、自动化描述。它将一个复杂的业务过程分解为一系列定义好的、可自动执行的步骤(或称为任务、活动),并规定了这些步骤之间的执行顺序、流转规则以及数据传递关系。
简单来说,工作流就是将“谁在什么时候做什么事”的规则,用计算机能理解的方式描述出来,并驱动其自动运行。例如,一个请假审批流程可以描述为:员工提交申请 → 直属经理审批 →(若请假天数>3天)部门总监审批 → HR备案 → 流程结束。
1.2 工作流的核心组件
一个典型的工作流引擎通常包含以下几个核心组件:
- 流程定义(Process Definition):业务流程的“蓝图”或“模板”,使用特定的建模语言(如BPMN 2.0)描述。它定义了流程的结构,包括有哪些任务、网关(决策点)、事件等。
- 流程实例(Process Instance):根据流程定义启动的一个具体运行实例。例如,员工张三发起一次请假,就创建了一个基于“请假流程”定义的流程实例。
- 活动/任务(Activity/Task):流程中的每一个步骤单元,如“填写表单”、“发送邮件”、“调用API”。
- 网关(Gateway):控制流程分支与合并的节点,如并行网关(所有分支同时执行)、排他网关(仅执行条件为真的一个分支)。
- 流转线(Sequence Flow):连接各个元素,指明执行顺序的箭头。
- 工作流引擎(Workflow Engine):负责解释流程定义、创建和管理流程实例、推动流程按规则流转的核心执行器。
1.3 为什么需要工作流?
引入工作流主要带来以下价值:
- 提升效率与自动化:将重复、规则明确的业务流程自动化,减少人工干预和等待时间。
- 提高过程可控性与透明度:每个流程实例的状态、当前任务、处理人、处理历史都清晰可查,便于管理和审计。
- 增强灵活性与可维护性:当业务流程需要变更时,通常只需修改流程定义(蓝图),而无需大规模改动底层业务代码。
- 促进业务与IT的协作:使用BPMN等标准图形化建模语言,业务人员也能参与流程设计,降低沟通成本。
理解了这些基础,我们就可以开始动手,学习如何“编辑”(设计)和“执行”(运行)一个工作流了。
2. 环境准备与工具选型
在开始实战前,我们需要搭建开发环境并选择一个合适的工作流引擎。市面上有很多优秀的开源工作流引擎,如Flowable,Activiti,Camunda等,它们都基于BPMN 2.0标准,功能强大。本文将以Flowable为例进行演示,因为它社区活跃,与Spring Boot集成友好,且同时支持BPMN(业务流程)和CMMN(案例管理)标准。
2.1 基础环境要求
- 操作系统:Windows 10/11, macOS, 或 Linux (如 Ubuntu 20.04+)
- Java开发环境:JDK 8 或 11 (推荐11, LTS版本)
- 构建工具:Maven 3.6+ 或 Gradle 6.x+
- IDE:IntelliJ IDEA, Eclipse 或 VS Code (需安装Java插件)
- 数据库:Flowable支持多种数据库,如 H2 (内存数据库,适合演示), MySQL 5.7+, PostgreSQL 10+。本文演示使用内嵌的H2数据库。
2.2 创建Spring Boot项目
我们使用 Spring Initializr 快速生成项目骨架。
- 访问 Spring Initializr。
- 选择项目信息:
- Project: Maven Project
- Language: Java
- Spring Boot: 选择最新的稳定版 (如 2.7.x 或 3.x, 注意JDK版本对应关系)
- 添加依赖:搜索并添加
Spring Web,Flowable,H2 Database,Lombok(可选,简化代码)。 - 点击“Generate”下载项目压缩包,解压后用IDE打开。
生成的pom.xml中应包含类似以下依赖(版本号可能不同):
<!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Flowable Spring Boot Starter --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.7.2</version> <!-- 请使用最新稳定版 --> </dependency> <!-- H2 数据库 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>2.3 基础配置
在src/main/resources/application.properties文件中进行简单配置:
# 应用端口 server.port=8080 # H2 数据库配置 (控制台可用于查看流程数据) spring.datasource.url=jdbc:h2:mem:flowable-db;DB_CLOSE_DELAY=-1 spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # 启用H2控制台,方便调试 spring.h2.console.enabled=true spring.h2.console.path=/h2-console # Flowable 配置 flowable.async-executor-activate=false # 演示关闭异步执行器 flowable.database-schema-update=true # 自动创建/更新表结构启动应用后,可以访问http://localhost:8080/h2-console查看数据库,JDBC URL填写jdbc:h2:mem:flowable-db。
3. 工作流编辑:使用BPMN 2.0设计流程
“编辑”工作流,即使用建模工具设计流程定义。我们可以使用Flowable提供的在线设计器、Eclipse插件,或者直接编写BPMN 2.0 XML文件。对于开发者,理解BPMN XML结构至关重要。
3.1 BPMN 2.0 基础元素
BPMN 2.0是一种XML标准,但我们可以通过图形化来理解它。一个最简单的顺序流程包含:
- 开始事件(Start Event):圆形,绿色,表示流程开始。
- 用户任务(User Task):圆角矩形,表示需要人工参与的任务。
- 服务任务(Service Task):圆角矩形,带齿轮图标,表示自动执行的服务或逻辑。
- 结束事件(End Event):圆形,红色,表示流程结束。
- 顺序流(Sequence Flow):带箭头的实线,连接各个元素。
3.2 创建第一个流程定义文件
在src/main/resources/processes/目录下(如果没有则创建),新建一个XML文件simple-approval.bpmn20.xml。
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:flowable="http://flowable.org/bpmn" targetNamespace="http://flowable.org/bpmn"> <!-- 定义一个流程,id是代码中引用的标识,name是显示名称 --> <process id="simpleApproval" name="简单审批流程" isExecutable="true"> <!-- 1. 开始事件 --> <startEvent id="startEvent" name="开始"/> <!-- 2. 第一个用户任务:提交申请 --> <userTask id="submitTask" name="提交请假申请" flowable:assignee="${applicant}"> <documentation>员工填写请假信息</documentation> </userTask> <!-- 3. 排他网关:根据条件决定流向 --> <exclusiveGateway id="decisionGateway" name="审批决策"/> <!-- 4. 经理审批任务 --> <userTask id="managerApproveTask" name="经理审批" flowable:candidateGroups="managers"> <documentation>直属经理审批申请</documentation> </userTask> <!-- 5. 服务任务:自动发送通知 --> <serviceTask id="sendNotificationTask" name="发送通知" flowable:class="org.example.flowable.service.SendNotificationService"> </serviceTask> <!-- 6. 结束事件 --> <endEvent id="endEvent" name="结束"/> <!-- 定义顺序流 --> <!-- 从开始到提交 --> <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitTask"/> <!-- 从提交到决策网关 --> <sequenceFlow id="flow2" sourceRef="submitTask" targetRef="decisionGateway"/> <!-- 决策网关到经理审批(默认流,无条件) --> <sequenceFlow id="flow3" sourceRef="decisionGateway" targetRef="managerApproveTask"> <!-- 可以在这里添加条件,例如: <conditionExpression xsi:type="tFormalExpression">${days > 3}</conditionExpression> --> </sequenceFlow> <!-- 从经理审批到发送通知 --> <sequenceFlow id="flow4" sourceRef="managerApproveTask" targetRef="sendNotificationTask"/> <!-- 从发送通知到结束 --> <sequenceFlow id="flow5" sourceRef="sendNotificationTask" targetRef="endEvent"/> </process> </definitions>关键点解释:
isExecutable="true":必须设置为true,否则引擎不会部署。flowable:assignee="${applicant}":任务处理人,使用表达式动态指定。${applicant}是一个流程变量。flowable:candidateGroups="managers":任务候选组,组名为“managers”的用户都可以认领此任务。flowable:class:指定服务任务执行时调用的Java类全限定名。
3.3 使用Flowable Modeler进行可视化编辑(可选)
对于复杂流程,可视化编辑更高效。你可以将Flowable Modeler(一个Web应用)集成到你的Spring Boot应用中,或者使用独立的桌面建模工具。
- 在
pom.xml中添加设计器依赖:<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-ui-modeler</artifactId> <version>6.7.2</version> </dependency> - 启动应用后,访问
http://localhost:8080/flowable-modeler即可进行拖拽式设计。设计完成后,可以导出BPMN XML文件,放入项目的resources/processes/目录。
4. 工作流执行:部署、启动与任务处理
流程设计好之后,下一步就是通过代码来“执行”它。这包括部署流程定义、启动流程实例、查询和处理任务等。
4.1 核心服务注入
Flowable Spring Boot Starter会自动配置一系列核心服务Bean,我们直接注入使用即可。
// 文件路径:src/main/java/org/example/flowable/service/ProcessService.java package org.example.flowable.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.repository.Deployment; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; @Service @Slf4j @RequiredArgsConstructor public class ProcessService { // 仓库服务:管理流程定义(BPMN文件)的部署、查询等。 private final RepositoryService repositoryService; // 运行时服务:管理正在运行的流程实例,负责启动、触发信号等。 private final RuntimeService runtimeService; // 任务服务:管理流程中的人工任务,如查询、完成、指派等。 private final TaskService taskService; // 其他服务:HistoryService(历史), ManagementService(管理)等按需注入 }4.2 部署流程定义
部署是将BPMN文件解析并持久化到数据库的过程,一个流程定义可以部署多次,产生多个部署版本。
// 在 ProcessService 中添加方法 public String deployProcess(String bpmnFileName) { // 从 classpath 的 processes/ 目录下加载文件 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/" + bpmnFileName) .name("简单审批流程部署") .deploy(); // 执行部署 log.info("流程部署成功!部署ID: {}, 部署名称: {}, 部署时间: {}", deployment.getId(), deployment.getName(), deployment.getDeploymentTime()); return deployment.getId(); }4.3 启动流程实例
部署成功后,就可以根据流程定义的ID来启动一个具体的流程实例了。启动时可以传入业务数据作为流程变量。
// 在 ProcessService 中添加方法 @Transactional public ProcessInstance startProcessInstance(String processDefinitionKey, String businessKey, Map<String, Object> variables) { // processDefinitionKey 是BPMN文件中 <process id="simpleApproval"> 的id // businessKey 通常关联业务主键,如请假单ID ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables); log.info("流程实例启动成功!实例ID: {}, 业务Key: {}, 定义ID: {}", processInstance.getId(), processInstance.getBusinessKey(), processInstance.getProcessDefinitionId()); return processInstance; }4.4 查询与处理用户任务
流程运行后,会在人工任务节点暂停,等待用户处理。
// 在 ProcessService 中添加方法 // 1. 查询某个用户或候选组的待办任务 public List<Task> getTasksByCandidateUser(String candidateUser) { return taskService.createTaskQuery() .taskCandidateUser(candidateUser) // 候选用户 .orderByTaskCreateTime().desc() // 按创建时间倒序 .list(); } public List<Task> getTasksByCandidateGroup(String candidateGroup) { return taskService.createTaskQuery() .taskCandidateGroup(candidateGroup) // 候选组 .orderByTaskCreateTime().desc() .list(); } // 2. 认领任务(将候选任务分配给具体用户) public void claimTask(String taskId, String userId) { taskService.claim(taskId, userId); log.info("任务 {} 已被用户 {} 认领", taskId, userId); } // 3. 完成任务,并传递任务变量(如表单数据、审批意见) @Transactional public void completeTask(String taskId, Map<String, Object> taskVariables) { if (taskVariables != null && !taskVariables.isEmpty()) { taskService.complete(taskId, taskVariables); } else { taskService.complete(taskId); } log.info("任务 {} 已完成", taskId); }4.5 实现服务任务逻辑
服务任务是自动执行的,需要实现一个Java类。该类必须实现org.flowable.engine.delegate.JavaDelegate接口。
// 文件路径:src/main/java/org/example/flowable/service/SendNotificationService.java package org.example.flowable.service; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; @Component("sendNotificationService") // Bean名称与BPMN中 flowable:delegateExpression="${sendNotificationService}" 对应 @Slf4j public class SendNotificationService implements JavaDelegate { @Override public void execute(DelegateExecution execution) { // 可以从 execution 中获取流程变量 String processInstanceId = execution.getProcessInstanceId(); String businessKey = (String) execution.getVariable("businessKey"); String applicant = (String) execution.getVariable("applicant"); // 模拟发送通知的逻辑 String message = String.format("【流程通知】流程实例[%s], 业务单[%s], 申请人[%s]的审批流程已结束。", processInstanceId, businessKey, applicant); log.info(message); // 实际项目中,这里可以调用邮件、短信、消息队列等服务 // emailService.send(...); } }注意:在BPMN XML中,我们之前用的是flowable:class属性直接指定类名。更灵活的方式是使用flowable:delegateExpression="${sendNotificationService}",这样可以直接引用Spring容器中的Bean。
5. 完整实战:构建一个请假审批REST API
现在,我们将上述所有步骤整合,通过几个简单的REST API来体验工作流的完整生命周期。
5.1 创建控制器
// 文件路径:src/main/java/org/example/flowable/controller/WorkflowController.java package org.example.flowable.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.example.flowable.service.ProcessService; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/workflow") @Slf4j @RequiredArgsConstructor public class WorkflowController { private final ProcessService processService; // 1. 部署流程 @PostMapping("/deploy") public String deploy(@RequestParam String bpmnFileName) { return processService.deployProcess(bpmnFileName); } // 2. 启动一个请假流程实例 @PostMapping("/start-leave") public Map<String, Object> startLeaveProcess(@RequestBody StartProcessRequest request) { Map<String, Object> variables = new HashMap<>(); variables.put("applicant", request.getApplicant()); variables.put("days", request.getDays()); variables.put("reason", request.getReason()); // businessKey 可以关联你的业务表ID,这里简单处理 String businessKey = "LEAVE_" + System.currentTimeMillis(); ProcessInstance instance = processService.startProcessInstance("simpleApproval", businessKey, variables); Map<String, Object> result = new HashMap<>(); result.put("processInstanceId", instance.getId()); result.put("businessKey", businessKey); return result; } // 3. 查询我的待办任务 (根据候选人用户) @GetMapping("/my-tasks") public List<Task> getMyTasks(@RequestParam String userId) { return processService.getTasksByCandidateUser(userId); } // 4. 查询组待办任务 (例如,所有经理的审批任务) @GetMapping("/group-tasks") public List<Task> getGroupTasks(@RequestParam String group) { return processService.getTasksByCandidateGroup(group); } // 5. 完成任务 (例如,经理审批) @PostMapping("/complete-task") public String completeTask(@RequestBody CompleteTaskRequest request) { Map<String, Object> taskVars = new HashMap<>(); taskVars.put("approvalResult", request.getApprovalResult()); // "approved" or "rejected" taskVars.put("comment", request.getComment()); processService.completeTask(request.getTaskId(), taskVars); return "任务处理完成"; } // 内部请求对象 @Data // 使用Lombok注解,需在pom中引入 static class StartProcessRequest { private String applicant; private Integer days; private String reason; } @Data static class CompleteTaskRequest { private String taskId; private String approvalResult; private String comment; } }5.2 运行与测试
- 启动Spring Boot应用。
- 部署流程:使用Postman或curl调用
POST http://localhost:8080/api/workflow/deploy?bpmnFileName=simple-approval.bpmn20.xml。 - 启动流程实例:调用
POST http://localhost:8080/api/workflow/start-leave,Body为JSON:
返回的{ "applicant": "zhangsan", "days": 5, "reason": "年假" }processInstanceId需要记下。 - 查询经理待办:调用
GET http://localhost:8080/api/workflow/group-tasks?group=managers。此时应该能看到一个“经理审批”任务。 - 处理任务:从上一步获取
taskId,调用POST http://localhost:8080/api/workflow/complete-task,Body为:{ "taskId": "获取到的taskId", "approvalResult": "approved", "comment": "同意" } - 观察日志:在应用控制台,你应该能看到
SendNotificationService打印的通知日志,表示流程已执行到服务任务并结束。
通过H2控制台 (http://localhost:8080/h2-console),你可以查询ACT_RU_TASK(运行中任务)、ACT_HI_PROCINST(历史流程实例)等表,直观看到流程数据的变化。
6. 常见问题与排查思路
在实际开发和集成工作流时,你可能会遇到以下典型问题。
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
流程部署失败,报XML parsing error | 1. BPMN XML文件语法错误。 2. 使用了引擎不支持的BPMN元素或属性。 | 1. 使用在线BPMN验证器或Flowable Modeler检查XML。 2. 检查Flowable官方文档,确认所用元素和属性是否被支持。 |
启动流程实例失败,报no processes deployed with key 'xxx' | 1. 流程定义KeyprocessDefinitionKey拼写错误。2. 流程定义未部署成功或已被挂起。 | 1. 检查BPMN文件中<process id="...">的值。2. 通过 repositoryService.createProcessDefinitionQuery().list()查询已部署的流程定义列表。 |
| 用户任务查询不到 | 1. 任务候选人/组设置错误。 2. 任务已被他人认领。 3. 流程未运行到该任务节点。 | 1. 检查BPMN中flowable:assignee或flowable:candidateGroups的值和传入的查询参数。2. 查询 ACT_RU_TASK表确认任务状态和负责人。3. 使用 runtimeService.createActivityInstanceQuery()查看流程当前活动节点。 |
| 服务任务/JavaDelegate未执行 | 1. 实现类未正确实现JavaDelegate接口或未被Spring管理。2. BPMN中 flowable:class类名错误或flowable:delegateExpression表达式错误。3. 方法内抛出未捕获的异常。 | 1. 确保类上有@Component注解,并实现了execute方法。2. 检查类路径和Bean名称。使用 delegateExpression时,表达式应为${beanName}。3. 在 execute方法内添加 try-catch 并打印详细日志。 |
| 流程变量获取为null | 1. 变量未在启动流程或完成任务时正确设置。 2. 变量作用域问题(流程实例变量 vs 任务局部变量)。 | 1. 检查设置变量的代码,确保Map被正确传递。 2. 使用 execution.getVariable()获取的是流程实例变量。任务变量需用taskService.getVariable()。理解变量作用域。 |
| 事务回滚导致流程状态不一致 | 在服务任务Delegate中操作数据库失败,导致整体事务回滚,但流程引擎可能已推进。 | 考虑将业务操作和流程引擎操作放在不同的事务中,或使用流程引擎的异步执行器。仔细设计异常处理逻辑。 |
7. 最佳实践与工程建议
将工作流集成到生产系统时,遵循以下最佳实践可以避免很多坑。
7.1 流程设计规范
- 保持流程简洁:一个流程应专注于一个核心业务目标。过于复杂的流程应拆分为子流程。
- 使用有意义的ID和Name:BPMN元素的ID在代码中引用,应保持稳定且有意义(如
submitLeaveRequest)。Name用于显示,应清晰描述业务动作。 - 合理使用网关:明确并行网关(
parallelGateway)和排他网关(exclusiveGateway)的使用场景。并行分支需同步,排他分支则互斥。 - 定义清晰的边界事件:对于超时、错误等异常情况,使用边界定时事件(
boundaryTimerEvent)或边界错误事件(boundaryErrorEvent)进行处理,使流程更健壮。
7.2 代码集成与架构
- 服务任务解耦:服务任务Delegate中应只包含协调逻辑,具体的业务操作(如调用外部API、复杂计算)应委托给独立的Spring Service Bean。这有利于测试和复用。
- 使用Delegate Expression而非Class:在BPMN中,优先使用
flowable:delegateExpression="${myService}"而不是flowable:class="...。这样可以利用Spring的依赖注入和AOP等特性。 - 流程变量管理:明确哪些变量是流程实例全局的,哪些是任务局部的。避免传递过大的对象作为变量,可以考虑只传递业务主键,在服务中根据主键查询完整数据。
- 业务键(Business Key)必填:启动流程实例时,务必传入有意义的
businessKey,它通常是关联业务实体(如订单号、请假单ID)的唯一标识,便于后续通过runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(key)快速查询。
7.3 性能与运维
- 数据库选择与优化:生产环境务必使用MySQL、PostgreSQL等外部数据库。定期清理历史数据(
ACT_HI_*表),Flowable提供了历史数据清理的配置和API。 - 异步执行器:对于非关键路径或耗时长的自动任务(如服务任务),启用异步执行器(
asyncExecutor),避免阻塞流程引擎线程。 - 监控与日志:集成Spring Boot Actuator,暴露Flowable的Health指标和Metrics。在关键节点(流程启动、任务创建完成、节点流转)记录结构化日志,便于问题追踪和业务分析。
- 版本控制与迁移:流程定义变更后,新部署会产生新版本。默认情况下,新发起的流程实例会使用最新版本。对于已运行的旧版本实例,需要有明确的迁移或完结策略。
7.4 安全与权限
- 任务权限控制:结合Spring Security,实现基于用户、角色、部门的任务查询和操作权限控制。Flowable的
TaskService查询API支持丰富的权限过滤条件。 - 流程启动权限:在Controller层或使用Flowable的
RuntimeService前,校验当前用户是否有权限启动特定类型的流程。 - 变量安全性:不要通过流程变量传递敏感信息(如密码、密钥)。对于需要审计的审批意见等,应妥善存储。
工作流的编辑与执行是现代企业级应用开发的核心技能之一。通过本文,你掌握了从零开始使用Flowable和Spring Boot设计、部署、运行一个完整工作流的方法。从理解BPMN图元,到编写XML定义,再到通过核心服务API驱动流程运转,最后构建出可对外提供服务的RESTful接口,这条路径覆盖了大部分基础开发场景。记住,工作流引擎是工具,核心在于你对业务流程的抽象和建模能力。在真实项目中,先从简单的、核心的流程开始实践,逐步迭代,并始终关注流程的可维护性、性能和数据一致性。
