1. 项目概述:从零到一玩转Activiti Modeler
如果你正在接触工作流引擎,尤其是Activiti,那么“如何快速上手并可视化地设计、部署和管理流程”绝对是你绕不开的第一个坎。Activiti Modeler作为其官方提供的在线流程设计器,极大地降低了流程定义的门槛,让开发者能像画流程图一样,直观地完成BPMN 2.0规范的流程建模。但很多朋友在初次使用时,往往卡在“设计完流程之后怎么办”这一步——如何将画好的图部署成可运行的流程定义?又如何去启动、查询乃至删除这些流程实例?这个过程如果仅靠零散的文档,很容易让人摸不着头脑。
今天,我就以一个实际可运行的项目为蓝本,带你完整走一遍使用Activiti Modeler进行流程创建、编辑、部署,并最终通过代码对流程实例进行全生命周期管理的实战路径。这不是一个简单的功能罗列,而是融合了我多次在项目中落地Activiti时踩过的坑、总结的最佳实践,以及如何让Modeler与后端服务无缝衔接的深度解析。无论你是刚接触工作流的新手,还是想优化现有流程管理方式的开发者,这篇内容都能给你提供一套即拿即用的解决方案。
2. Activiti Modeler核心定位与项目环境搭建
2.1 为什么选择Activiti Modeler?
在开始动手之前,我们得先搞清楚Activiti Modeler在我们的技术栈里扮演什么角色。Activiti本身是一个强大的工作流和业务流程管理(BPM)引擎,但它核心是一个Java库,流程定义本身是以XML格式(符合BPMN 2.0标准)存在的。直接手写或编辑这些XML对于复杂流程来说,不仅效率低下,而且极易出错。
Activiti Modeler正是为了解决这个问题而生。它是一个基于Web的图形化设计器,允许你通过拖拽组件(如用户任务、网关、服务任务等)来绘制流程图,并自动生成对应的BPMN 2.0 XML。它的核心价值在于:
- 可视化建模:降低学习成本,业务分析师也能参与初步设计。
- 标准化输出:确保生成的流程定义文件严格符合BPMN 2.0规范,能被Activiti引擎正确解析。
- 集成性:它可以作为一个独立应用运行,也可以被嵌入到你自己的Spring Boot等Web应用中。
在我们的项目上下文中,Modeler主要承担“流程设计器”的职责。我们用它来创建和编辑流程模型(Model),然后将其“部署”(Deploy)到Activiti引擎中,使之成为可执行的流程定义(Process Definition)。后续的流程实例(Process Instance)启动、任务处理、实例删除等操作,则通过我们编写的后端服务代码来完成。
2.2 项目基础环境与依赖配置
为了让整个流程“可运行”,我们需要搭建一个包含Activiti Modeler和后端引擎的完整环境。这里我推荐使用Spring Boot来快速集成,这是目前最主流的方案。
首先,创建一个标准的Spring Boot项目。在你的pom.xml文件中,需要引入关键依赖:
<dependencies> <!-- Spring Boot Web 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Activiti Spring Boot Starter 集成 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> <!-- 请使用与Spring Boot版本兼容的稳定版 --> </dependency> <!-- 数据库驱动,这里以H2内存数据库为例,方便演示 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- 如果需要,也可以引入MySQL驱动 --> <!-- <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> --> </dependencies>关键点解析:
activiti-spring-boot-starter:这个starter会自动配置Activiti引擎、Spring Process Engine以及相关的服务(如RepositoryService、RuntimeService等)。它会自动创建所需的数据库表。- 数据库选择:Activiti需要数据库来存储流程定义、实例、任务等数据。示例中使用H2内存数据库,重启后数据会丢失,适合演示和测试。生产环境务必换成MySQL、PostgreSQL等持久化数据库。只需更换驱动和配置
application.properties中的数据库连接信息即可。
接下来,在application.properties中做基本配置:
# 应用端口 server.port=8080 # H2数据库配置(内存模式) spring.datasource.url=jdbc:h2:mem:activiti-db;DB_CLOSE_DELAY=-1 spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # 启动时自动创建表(第一次运行需要) spring.datasource.initialization-mode=always # Activiti 配置 # 关闭自动部署(我们将通过Modeler和API手动部署) spring.activiti.database-schema-update=true spring.activiti.check-process-definitions=false注意事项:
spring.activiti.database-schema-update=true:设置为true,引擎启动时会自动检查并创建/更新数据库表结构。生产环境建议设置为false或使用create-drop等更可控的策略。spring.activiti.check-process-definitions=false:关闭Spring Boot启动时自动扫描processes目录下的BPMN文件进行部署。因为我们打算通过Modeler上传或API部署,所以这里关闭。
至此,一个集成了Activiti引擎的Spring Boot后端环境就准备好了。接下来,我们需要把Activiti Modeler集成进来。
3. 集成Activiti Modeler进行流程可视化设计
3.1 获取与集成Activiti Modeler
Activiti Modeler是Activiti项目的一部分。对于Spring Boot项目,一个相对简便的方式是直接使用Activiti官方提供的UI模块,或者从前端工程入手。这里我介绍一种更直接、更贴近实战的集成方法:将Modeler的静态资源引入到我们的项目中。
- 获取静态资源:你可以从Activiti的GitHub仓库(例如 activiti/activiti-ui 项目)的发布版本中,找到编译好的Modeler前端资源(通常是一个包含HTML、JS、CSS的文件夹)。或者,更简单的方式是,在网络上寻找已经打包好的、可用于独立部署的Activiti Modeler WAR包或静态资源包。
- 放置资源:将获取到的Modeler相关静态文件(如
index.html,editor.html,scripts,styles等目录)复制到Spring Boot项目的src/main/resources/static目录下。例如,你可以创建一个src/main/resources/static/modeler文件夹,把所有文件放进去。 - 配置视图解析(可选):如果你希望直接通过根路径访问,可以简单配置。但更常见的做法是,我们编写一个简单的Controller来重定向到Modeler的入口页面。
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class ModelerController { @GetMapping("/modeler") public String modeler() { // 假设你的Modeler入口页面是 static/modeler/editor.html return "redirect:/modeler/editor.html"; } }启动Spring Boot应用,访问http://localhost:8080/modeler,你应该就能看到Activiti Modeler的设计界面了。
实操心得:
- 网络上找到的Modeler资源版本可能与你使用的Activiti引擎版本不完全匹配,可能导致一些高级特性无法使用或出现兼容性问题。最佳实践是使用与你的
activiti-spring-boot-starter版本相匹配的Modeler资源。通常,大版本号一致(如7.x)的基础功能是兼容的。 - Modeler默认可能需要连接一个“模型API”后端来保存模型数据。对于简单集成,我们可以先专注于其“设计并导出BPMN XML”的功能。更复杂的集成(如模型保存、导入)需要部署额外的Activiti REST服务或自行实现对应接口。
3.2 使用Modeler创建与编辑第一个流程
打开Modeler界面,你会看到一个画布和左侧的工具栏。我们来创建一个简单的请假流程作为示例。
- 创建新模型:点击“创建模型”或类似按钮,输入模型名称(如“员工请假流程”)和描述。
- 拖拽组件:
- 从左侧面板拖一个
Start Event(开始事件)到画布。 - 拖一个
User Task(用户任务)到画布,将其命名为“提交请假申请”。双击任务节点,可以在右侧属性面板配置“Assignee”(受理人),这里我们先填一个静态值,比如“employee”。在实际项目中,这里通常会配置为表达式,如${applyUserId}。 - 再拖一个
User Task,命名为“经理审批”,Assignee设为“manager”。 - 拖一个
Exclusive Gateway(排他网关)到画布。 - 拖一个
End Event(结束事件)到画布。
- 从左侧面板拖一个
- 连接序列流:使用连接线工具,将各个节点按顺序连接起来:开始事件 -> 提交申请 -> 排他网关 -> 经理审批 -> 结束事件。同时,从排他网关直接拉一条线到结束事件(用于审批驳回等场景)。
- 配置网关条件:点击从排他网关到“经理审批”的序列流,在属性面板中,找到“Condition”条件,选择“Expression”,并输入一个简单的表达式,例如
${approval == true}。这意味着当流程变量approval为true时,会走这条审批通过的路径。另一条流向结束的线可以配置为默认流(Default flow)。 - 保存与导出:设计完成后,点击保存按钮(如果集成了后端保存功能)。最关键的一步:找到“导出”或“下载”功能,选择导出为“BPMN 2.0 XML”格式。这会下载一个
.bpmn20.xml或.bpmn文件。用文本编辑器打开这个文件,你会看到它就是一个符合BPMN 2.0标准的XML描述文件,这就是Activiti引擎能识别的流程定义。
避坑指南:
- 任务指派:在Modeler中直接写死的Assignee(如“manager”)在演示时可行,但在真实业务中极不灵活。正确的做法是使用UEL表达式,例如
${departmentManager},然后在启动流程或完成任务时,通过代码动态设置这个变量的值。 - 网关使用:排他网关(Exclusive Gateway)用于决策(多选一),并行网关(Parallel Gateway)用于同时触发多个分支。务必理清业务逻辑,选择正确的网关类型。连线上的条件表达式是流程流转的核心逻辑。
- 导出文件:务必通过Modeler的“导出”功能获取BPMN XML文件。直接复制画布或截图是没用的,引擎只认XML。
4. 流程部署:将设计图转化为引擎可执行的定义
拿到BPMN XML文件后,它只是一个静态的模型文件。下一步就是将其“部署”到Activiti引擎中,使其成为一个可被启动的“流程定义”。
4.1 通过RepositoryService进行部署
Activiti提供了RepositoryService来管理流程定义和部署。我们创建一个Spring Bean(如一个Controller或Service)来调用它。
import org.activiti.api.process.model.ProcessDefinition; import org.activiti.api.process.runtime.ProcessRuntime; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.charset.StandardCharsets; @RestController @RequestMapping("/api/process") public class ProcessDeployController { @Autowired private RepositoryService repositoryService; @Autowired private ProcessRuntime processRuntime; // 用于查询已部署的定义 /** * 方式一:通过上传BPMN XML文件进行部署 */ @PostMapping("/deploy-by-file") public String deployByUpload(@RequestParam("file") MultipartFile file) throws IOException { if (file.isEmpty()) { return "部署失败:文件为空"; } String fileName = file.getOriginalFilename(); // 进行简单的文件类型校验 if (fileName != null && !(fileName.endsWith(".bpmn") || fileName.endsWith(".bpmn20.xml"))) { return "部署失败:仅支持.bpmn或.bpmn20.xml文件"; } Deployment deployment = repositoryService.createDeployment() .addBytes(fileName, file.getBytes()) // 使用文件字节流 .name("部署自上传文件:" + fileName) .deploy(); // 执行部署 return "部署成功!部署ID: " + deployment.getId() + ", 部署名称: " + deployment.getName(); } /** * 方式二:从项目资源路径下读取固定文件进行部署(适合预定义流程) */ @PostMapping("/deploy-predefined") public String deployPredefined() throws IOException { Resource resource = new ClassPathResource("processes/leave-application.bpmn20.xml"); if (!resource.exists()) { return "部署失败:资源文件不存在"; } String bpmnXmlContent = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); Deployment deployment = repositoryService.createDeployment() .addString("leave-application.bpmn20.xml", bpmnXmlContent) // 使用字符串内容 .name("员工请假流程预部署") .deploy(); return "预定义流程部署成功!部署ID: " + deployment.getId(); } }关键代码解析:
repositoryService.createDeployment():创建一个部署构建器。.addBytes()/.addString():添加部署资源。你可以添加文件字节流,也可以直接添加XML字符串。支持同时添加多个资源(如流程主图、用户任务表单等)。.name():为本次部署设置一个名称,便于管理。.deploy():执行部署操作。这是一个同步方法,执行成功后,流程定义就已经存入数据库,并可以被查询和启动了。
部署成功后,你可以通过repositoryService.createProcessDefinitionQuery().list()来查询所有已部署的流程定义,验证部署是否成功。
4.2 部署结果管理与版本控制
每次执行deploy(),只要流程的key(在BPMN XML文件的<process>标签的id属性)相同,Activiti就会将其视为同一流程的不同版本。引擎会自动为这个流程定义生成一个新的版本号(从1开始递增),并停用旧版本。
// 查询所有流程定义 List<ProcessDefinition> definitions = processRuntime.processDefinitions(Pageable.of(0, 10)).getContent(); for (ProcessDefinition pd : definitions) { System.out.println("ID: " + pd.getId() + ", Key: " + pd.getKey() + ", Name: " + pd.getName() + ", Version: " + pd.getVersion()); }这个特性对于流程的迭代更新非常有用。当你修复了一个流程错误或优化了审批节点后,只需重新部署新的BPMN文件,新发起的流程实例就会自动使用最新版本,而正在运行的老版本实例则不受影响,继续按原定义执行。
注意事项:
- 流程定义Key:BPMN XML中
<process id="myProcess" ...>的id属性,就是流程定义的Key。它是流程的唯一业务标识,版本更替时Key不变。 - 流程定义ID:部署后,Activiti会生成一个全局唯一的ID,格式通常为
{key}:{version}:{随机数}。在启动流程实例时,既可以使用Key(启动最新版本),也可以使用这个具体的ID(启动指定版本)。
5. 流程实例的启动、查询与删除实战
流程定义部署好后,就相当于在引擎里“注册”了一个流程模板。真正的业务流程是从启动一个“流程实例”开始的。
5.1 启动流程实例
我们使用RuntimeService来启动和管理流程实例。
import org.activiti.engine.RuntimeService; import org.activiti.engine.runtime.ProcessInstance; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/instance") public class ProcessInstanceController { @Autowired private RuntimeService runtimeService; /** * 启动一个流程实例 * @param processDefinitionKey 流程定义Key * @param businessKey 业务唯一标识,例如请假单号 * @return 启动的流程实例信息 */ @PostMapping("/start") public Map<String, Object> startInstance(@RequestParam String processDefinitionKey, @RequestParam(required = false) String businessKey) { // 准备流程变量 Map<String, Object> variables = new HashMap<>(); variables.put("applyUserId", "zhangsan"); // 申请人ID variables.put("departmentManager", "lisi"); // 部门经理ID,用于任务分配 variables.put("days", 3); // 请假天数 variables.put("approval", null); // 审批结果,初始为null,由审批任务设置 // 启动流程实例 ProcessInstance instance; if (businessKey != null && !businessKey.trim().isEmpty()) { // 关联业务Key启动 instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables); } else { // 不关联业务Key启动 instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables); } Map<String, Object> result = new HashMap<>(); result.put("success", true); result.put("processInstanceId", instance.getId()); result.put("processDefinitionId", instance.getProcessDefinitionId()); result.put("businessKey", instance.getBusinessKey()); result.put("activityId", instance.getActivityId()); // 当前活动节点ID return result; } }关键点解析:
runtimeService.startProcessInstanceByKey():这是最常用的启动方式,使用流程定义Key来启动最新版本的流程定义。- 流程变量(Variables):在启动时传入的
variablesMap至关重要。这些变量在整个流程实例生命周期内都有效,可以用于:- 任务分配:在BPMN中,User Task的Assignee配置为
${departmentManager},引擎在创建任务时,会用这里传入的“lisi”去赋值。 - 网关条件判断:如前文所述,排他网关的条件
${approval == true},其中的approval变量就需要在审批任务完成后被设置。 - 业务数据传递:如
days(请假天数)可以在后续任务或监听器中读取。
- 任务分配:在BPMN中,User Task的Assignee配置为
- 业务键(Business Key):这是一个非常实用的字段,用于将Activiti流程实例与你的业务实体(如请假单、订单)关联起来。例如,你可以把请假单的数据库ID作为Business Key传入,这样以后就可以通过这个Key快速找到对应的流程实例。
5.2 查询与监控流程实例
流程实例启动后,我们需要能够查询它们的状态、当前节点等信息。
/** * 根据业务Key查询流程实例 */ @GetMapping("/query-by-businesskey") public List<Map<String, Object>> queryByBusinessKey(@RequestParam String businessKey) { List<ProcessInstance> instances = runtimeService.createProcessInstanceQuery() .processInstanceBusinessKey(businessKey) .list(); return instances.stream().map(instance -> { Map<String, Object> info = new HashMap<>(); info.put("id", instance.getId()); info.put("businessKey", instance.getBusinessKey()); info.put("definitionId", instance.getProcessDefinitionId()); info.put("activityId", instance.getActivityId()); // 当前停留的节点ID info.put("suspended", instance.isSuspended()); // 是否被挂起 return info; }).collect(Collectors.toList()); } /** * 查询某个用户待办的任务 */ @GetMapping("/my-tasks") public List<Map<String, Object>> getMyTasks(@RequestParam String userId) { List<Task> tasks = taskService.createTaskQuery() .taskAssignee(userId) // 查找指派给该用户的任务 .orderByTaskCreateTime().desc() // 按创建时间倒序 .list(); return tasks.stream().map(task -> { Map<String, Object> taskInfo = new HashMap<>(); taskInfo.put("taskId", task.getId()); taskInfo.put("taskName", task.getName()); taskInfo.put("processInstanceId", task.getProcessInstanceId()); taskInfo.put("createTime", task.getCreateTime()); // 可以进一步通过runtimeService获取流程变量,显示业务信息 // Map<String, Object> vars = runtimeService.getVariables(task.getProcessInstanceId()); // taskInfo.put("applyUser", vars.get("applyUserId")); // taskInfo.put("days", vars.get("days")); return taskInfo; }).collect(Collectors.toList()); }5.3 完成任务与驱动流程流转
流程实例启动后,会停留在第一个用户任务节点(“提交请假申请”)。我们需要模拟用户完成任务来驱动流程向下流转。
import org.activiti.engine.TaskService; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/task") public class TaskController { @Autowired private TaskService taskService; @Autowired private RuntimeService runtimeService; /** * 完成一个任务 * @param taskId 任务ID * @param approved 审批是否通过 */ @PostMapping("/complete") public String completeTask(@RequestParam String taskId, @RequestParam boolean approved) { // 在完成任务前,可以设置流程变量,这些变量会影响后续网关的走向 Map<String, Object> taskVariables = new HashMap<>(); taskVariables.put("approval", approved); // 设置审批结果变量 // 完成任务 taskService.complete(taskId, taskVariables); // 完成任务后,可以查询流程实例是否已到达下一个节点或结束 // 这里简单返回成功信息 return "任务完成成功。流程继续流转。"; } /** * 认领任务(如果任务没有被指定受理人,或者需要拾取) */ @PostMapping("/claim") public String claimTask(@RequestParam String taskId, @RequestParam String userId) { taskService.claim(taskId, userId); return "任务认领成功"; } }核心逻辑:
- 任务查询:用户“lisi”(部门经理)登录系统后,调用
/api/instance/my-tasks?userId=lisi,会看到指派给他的“经理审批”任务。 - 任务执行:前端展示任务列表,用户点击处理,调用
/api/task/complete接口,传入任务ID和审批结果(approved=true/false)。 - 流程推进:
taskService.complete()方法会结束当前任务。引擎会根据任务出口序列流,计算下一个节点。由于我们设置了排他网关和条件${approval == true},引擎会判断变量approval的值,决定流程是走向“结束事件”(如果为false或默认流)还是走向下一个节点(如果为true)。在我们的简单流程里,审批通过后流程就结束了。 - 变量传递:在
complete方法中传入的taskVariables会设置到流程实例的上下文中,对后续所有节点可见。
5.4 删除流程实例:谨慎操作与业务考量
删除流程实例是一个需要慎重的操作,因为它会清除该实例的所有运行时数据(任务、变量、历史记录等)。通常用于管理异常流程或测试数据清理。
/** * 删除流程实例(及其历史记录) * @param processInstanceId 流程实例ID * @param deleteReason 删除原因,会记录在历史中 */ @DeleteMapping("/delete") public String deleteInstance(@RequestParam String processInstanceId, @RequestParam String deleteReason) { try { // 使用RuntimeService删除运行时实例 runtimeService.deleteProcessInstance(processInstanceId, deleteReason); // 注意:deleteProcessInstance默认会保留历史记录。 // 如果需要彻底删除(包括历史记录),需要使用HistoryService // historyService.deleteHistoricProcessInstance(processInstanceId); return "流程实例删除成功。"; } catch (ActivitiObjectNotFoundException e) { return "删除失败:未找到流程实例,ID=" + processInstanceId; } }重要警告与最佳实践:
- 业务状态同步:如果你的业务系统有自己的状态(如“请假单状态”为“审批中”),在删除Activiti流程实例前,务必先更新业务系统的状态为“已取消”或“异常终止”,保持数据一致性。
- 删除原因:务必填写有意义的
deleteReason,例如“申请人撤销”、“系统异常终止”,这对于后续审计至关重要。 - 历史记录:
runtimeService.deleteProcessInstance()方法默认会将该实例移入历史表(ACT_HI_*),这意味着你仍然可以从历史中查询到它。如果调用historyService.deleteHistoricProcessInstance(),则是物理删除,数据将不可恢复。生产环境慎用物理删除。 - 级联删除:删除流程实例会级联删除其下的所有运行中的任务、变量等。
6. 常见问题排查与实战技巧实录
在实际集成和开发过程中,你肯定会遇到各种问题。下面是我总结的一些典型场景和解决方案。
6.1 Modeler设计与引擎运行不一致
问题现象:在Modeler里流程画得好好的,一部署启动就报错,或者流转逻辑不对。
- 可能原因1:BPMN XML语法错误。虽然Modeler生成,但偶尔也可能因版本兼容性问题产生非标内容。
- 排查:将Modeler导出的XML文件用文本编辑器打开,检查关键节点(如
<process>,<userTask>,<sequenceFlow>)的id、name、sourceRef、targetRef属性是否完整、有无非法字符。可以尝试使用在线的BPMN 2.0验证工具进行校验。
- 排查:将Modeler导出的XML文件用文本编辑器打开,检查关键节点(如
- 可能原因2:表达式错误。在Assignee或条件中使用了表达式(如
${manager}),但启动流程时没有传入对应的变量。- 排查:检查所有使用了
${...}的地方。确保在启动流程实例或完成任务时,通过variablesMap传入了所有必需的变量。可以使用runtimeService.getVariables(processInstanceId)在运行时查看变量值。
- 排查:检查所有使用了
- 可能原因3:网关配置遗漏。排他网关的某条流出连线没有设置条件,也没有设置为默认流。
- 排查:对于排他网关,所有流出连线必须满足“有且仅有一条默认流,其余连线必须有条件”的规则。检查Modeler中每条连线的属性。
6.2 任务查询不到或分配错误
问题现象:明明启动了流程,但调用taskService.createTaskQuery().taskAssignee(“某人”).list()却返回空列表。
- 可能原因1:Assignee表达式未解析。在Modeler中,User Task的Assignee字段如果写的是表达式
${manager},那么引擎会在创建任务时,从当前流程变量中查找manager这个key的值,并将其作为Assignee。如果变量不存在或值为null,任务Assignee可能为null。- 解决:在启动流程或到达该任务的上一个节点时,务必确保设置了正确的流程变量。可以在任务创建监听器中打印日志来调试。
- 可能原因2:候选人(Candidate Users/Groups)设置。任务可能被分配给了候选人组或候选人,而不是具体的受理人。此时需要用
.taskCandidateUser()或.taskCandidateGroupIn()来查询。 - 可能原因3:流程尚未流转到用户任务节点。可能因为自动服务任务(Service Task)执行时间过长或出错,流程卡在了前面。
- 排查:使用
runtimeService.getActiveActivityIds(processInstanceId)查看流程实例当前活动的节点ID,与Modeler中的节点ID对比。
- 排查:使用
6.3 流程实例无法删除或出现外键约束错误
问题现象:调用删除接口时,抛出异常,提示有关联数据无法删除。
- 可能原因:存在未完成的子任务或关联实体。Activiti的数据表之间存在外键约束。
- 标准操作:优先使用
runtimeService.deleteProcessInstance(processInstanceId, reason)。这个方法会正确处理级联删除。 - 强制删除(极端情况):如果实例状态异常,标准删除失败,可以考虑先通过
taskService.deleteTasks(…)删除关联任务,再通过runtimeService.suspendProcessInstanceById(…)挂起实例,最后再尝试删除。但更推荐的方式是,直接操作数据库删除(仅限开发测试环境),按照ACT_RU_TASK->ACT_RU_IDENTITYLINK->ACT_RU_VARIABLE->ACT_RU_EXECUTION->ACT_RU_EVENT_SUBSCR->ACT_RU_JOB->ACT_RU_TIMER_JOB->ACT_RU_SUSPENDED_JOB->ACT_RU_DEADLETTER_JOB->ACT_RU_HISTORY_JOB->ACT_RU_EXECUTION的顺序进行清理,最后删除ACT_RU_PROCINST记录。生产环境严禁此操作。
- 标准操作:优先使用
6.4 性能优化与数据清理建议
随着流程实例数量的增长,运行时表(ACT_RU_*)和历史表(ACT_HI_*)会变得非常庞大。
- 历史数据归档:Activiti的历史级别(
history-level)可以在配置中设置。如果不需要完整的审计跟踪,可以设置为audit或activity,减少历史数据量。定期(如每月)将历史数据迁移到备份库,并从当前库中清理。 - 异步执行器:对于服务任务(Service Task)等,如果设置为
activiti:async=true,则会由异步执行器(Async Executor)处理,避免阻塞流程线程。确保你的异步执行器配置正确且运行正常。 - 流程定义缓存:Activiti会缓存已部署的流程定义。在频繁重新部署的测试环境,有时会遇到缓存未刷新的问题。可以调用
repositoryService.setProcessDefinitionCacheMaxSize(0)临时禁用缓存,或通过repositoryService.deleteDeployment(deploymentId, true)删除部署时级联清除缓存。
整个流程走下来,从Modeler画图到后端代码驱动,你会发现Activiti的核心思想是状态机和事件驱动。Modeler帮你定义状态和转移规则(BPMN),而你的代码则通过调用引擎API来触发状态转移(启动、完成任务)。理解这一点,就能更好地设计流程和处理各种边界情况。最后,一定要善用Activiti提供的各种Service(RepositoryService, RuntimeService, TaskService, HistoryService)进行查询和调试,它们是你与流程引擎交互的最主要工具。