1. 项目概述从流程图到可执行代码的旅程在任何一个涉及审批、流转或自动化处理的软件项目中流程引擎都是核心的“中枢神经系统”。我们经常在需求文档里看到用BPMN业务流程模型与标记法画的流程图那些圆角矩形、菱形、箭头看起来清晰明了。但很多开发者尤其是刚接触这块的朋友心里都会有个疑问这张漂亮的图到底是怎么变成系统中真正跑起来的代码的从设计到开发、测试、上线再到监控和优化这个“流程”本身经历了怎样的生命周期今天我就用一个最经典的“员工请假审批流程”作为示例带大家走一遍这个完整的旅程。我们会用到业界广泛使用的Activiti流程引擎但重点不在于某个框架的API调用而在于理解从“图”到“活系统”的完整闭环。无论你是前端、后端还是项目经理理解这个过程都能让你在涉及流程类需求时心里更有谱沟通更顺畅。2. 流程生命周期的全景透视在深入代码之前我们必须先建立起对流程生命周期的宏观认知。这绝不仅仅是“画个图部署一下”那么简单。一个健壮的流程其生命周期贯穿了项目的始终我们可以将其划分为四个核心阶段设计建模期、开发部署期、运行监控期和运维优化期。每个阶段都有其独特的关注点和产出物。2.1 设计建模期业务与技术的第一次握手这个阶段发生在任何代码编写之前是业务分析师、产品经理和架构师的主场。核心目标是将模糊的业务需求转化为精确的、可被流程引擎理解的模型。我们示例中的“员工请假审批流程”需求可能是这样的“员工提交请假申请时长小于等于3天需直属领导审批大于3天需直属领导和部门总监两级审批审批通过后系统自动扣减年假余额并通知员工审批不通过则流程结束并通知员工。”此时BPMN图就是沟通的“普通话”。我们会画出包含以下元素的流程图开始事件一个空心圆代表流程的触发点如员工点击“提交申请”按钮。用户任务圆角矩形代表需要人工参与的操作如“直属领导审批”、“部门总监审批”。网关菱形负责流程的路由决策。最常用的是排他网关它像程序中的if-else语句根据条件决定走哪条分支如判断请假天数 3。服务任务圆角矩形但内部有一个齿轮小图标代表自动执行的系统逻辑如“扣减年假余额”、“发送通知邮件”。结束事件一个粗边空心圆代表流程实例的终结。注意在画图时一个常见的误区是试图在一个流程图中涵盖所有异常情况和业务细节这会导致图形异常复杂。正确的做法是主流程清晰简洁将复杂的业务逻辑如计算请假天数是否包含节假日封装到服务任务背后的Java类或脚本中。BPMN负责描述“何时、由谁、做什么”具体的“怎么做”交给代码。2.2 开发部署期让模型“活”起来设计稿BPMN图完成后就交给了开发团队。这个阶段的目标是为静态的模型注入动态的生命使其能够在流程引擎中运行。首先我们需要将.bpmn或.bpmn20.xml文件即画好的流程图部署到流程引擎如Activiti中。部署动作不仅仅是上传一个文件引擎会做几件重要的事解析校验BPMN XML的语法和结构是否正确。持久化将流程定义的关键信息如ID、名称、版本、模型结构存入数据库通常是ACT_RE_PROCDEF表。缓存在内存中生成可执行的对象模型提升后续启动流程实例的速度。部署成功后我们就得到了一个流程定义。你可以把它理解为一个Java的Class。它定义了流程的结构但本身不会执行。当员工真正提交一张请假单时系统会基于这个“流程定义”启动一个流程实例。这就好比根据Classnew出来一个Object。这个实例拥有自己的状态、变量如applicant申请人、leaveDays请假天数、status状态并沿着流程图的路径一步步推进。每个任务用户任务或服务任务都会在数据库如ACT_RU_TASK运行时任务表中生成一条待办记录或待执行记录。2.3 运行监控期流程的“心电图”流程实例一旦启动就进入了运行期。这是生命周期中最活跃的阶段也是我们需要重点监控的阶段。对于用户任务引擎会生成待办事项。例如“直属领导审批”任务会产生一条待办出现在领导的待办列表里。领导通过前端界面点击“同意”或“拒绝”后端会调用引擎的completeTaskAPI并传递审批意见变量如approvalResult: ‘agree’。引擎接收到完成指令后会推动流程实例移动到下一个节点。对于服务任务引擎会自动执行我们预先绑定好的Java类Delegate或脚本。例如在“扣减年假余额”这个服务任务中我们绑定的Java类会从流程变量中读取applicant和leaveDays然后调用用户服务或直接操作数据库完成扣减操作。在这个过程中流程变量扮演了血液的角色在各个节点间传递数据。流程实例的状态运行中、已暂停、已结束和当前活动节点共同构成了流程的“心电图”。我们需要通过管理后台或监控API实时查看这些信息以了解业务进行到了哪一步是否有流程卡住例如某个审批任务长时间无人处理。2.4 运维优化期持续的迭代与调优流程上线并非终点。随着业务发展我们可能会发现流程节点设置不合理、审批链过长、自动化节点失败率高等问题。这时就进入了运维优化期。Activiti支持流程定义的版本管理。当你部署一个同名的、但修改过的BPMN文件时引擎会自动为其分配一个新版本号如从1.0升到2.0。默认情况下新发起的流程实例会使用最新版本而已经运行的旧实例将继续按旧版本的定义执行完毕。这实现了流程的平滑升级。此外我们还需要关注性能监控统计流程实例的平均完成时间、各节点的耗时找出瓶颈。错误处理为服务任务配置重试机制或错误边界事件避免因单个节点失败导致整个流程实例挂起。数据分析基于历史数据ACT_HI_系列表分析请假频率、审批通过率、各领导平均审批时长等为业务决策提供支持。3. 核心细节解析与实操要点理解了全景我们来深入看看几个最容易出问题的核心细节。这些地方如果理解不透彻开发时一定会踩坑。3.1 流程变量数据的载体与传递机制流程变量是流程实例的“记忆”。它本质上是一个键值对集合可以在流程启动时设置也可以在任务完成、服务任务执行时设置或修改。变量的作用域是一个关键概念流程实例变量作用域是整个流程实例在任何节点都可以存取。通常用于存储全局业务数据如applicant,leaveDays,startDate等。任务局部变量仅作用于某个特定的任务实例。任务完成后这些变量默认会消失。可用于存储临时数据但使用需谨慎。在请假流程中当领导审批时我们通常将审批意见approvalResult作为流程实例变量来传递因为后续的网关判断和通知任务都需要用到它。变量序列化是另一个要点。Activiti需要将变量值存储到数据库。对于基本类型String, Integer或实现了Serializable接口的Java对象引擎会自动处理。但对于复杂对象或频繁存取的对象直接存储可能带来性能问题。一种常见的优化模式是只将业务实体的ID如leaveOrderId作为流程变量存储在需要具体数据时再通过这个ID去查询业务数据库。3.2 网关流程的决策大脑网关控制着流程的走向。最常用的是排他网关和并行网关。排他网关用于“多选一”的场景。在请假流程中我们用它来判断请假天数。在网关的每条流出顺序流上都需要设置一个条件表达式通常使用UELUnified Expression Language来编写例如${leaveDays 3}。引擎会按顺序流定义的顺序评估条件第一条评估为true的路径将被执行。务必设置一条默认流不设条件作为所有条件都不满足时的出口避免流程卡死。并行网关用于“所有路径都必须执行”的场景比如需要同时通知申请人和HR。它不计算条件所有流出路径都会同时产生新的执行分支。所有分支都完成后才会汇聚到下一个节点。使用并行网关时要特别注意任务签收和完成的一致性避免因某个分支的任务无人处理而导致整个流程实例无法继续。3.3 任务分配明确“谁来做”用户任务必须明确指定处理人Assignee或候选组Candidate Group。指定方式有多种固定人员在BPMN图中直接写死activiti:assignee“zhangsan”。这种方式极不灵活仅用于演示或固定角色的场景。通过变量动态指定这是最常用的方式。例如activiti:assignee“${directLeader}”。在流程启动前或启动时我们需要将directLeader直属领导ID这个变量设置好。通过监听器指定创建一个任务创建监听器TaskListener在EVENTNAME_CREATE事件中编写复杂的逻辑来计算处理人例如从组织架构服务中查询当前申请人的上级领导。实操心得我强烈推荐使用“候选组”而非直接指定“处理人”。例如设置activiti:candidateGroups“dept:manager”。这样所有属于“dept:manager”这个组的用户都能看到这个任务并可以“签收”它使之成为自己的待办。这带来了灵活性领导出差时组内其他成员可以代为处理。前端界面则需要提供“签收任务”和“查询候选组任务”的功能。4. 实操过程与核心环节实现下面我们以Spring Boot集成Activiti 7为例看看如何将上述理论落地。这里不会罗列所有代码而是聚焦于几个最关键的操作。4.1 环境准备与流程部署首先在pom.xml中引入依赖。Activiti 7提供了Spring Boot Starter大大简化了配置。dependency groupIdorg.activiti/groupId artifactIdactiviti-spring-boot-starter/artifactId version7.1.0.M6/version !-- 请使用最新稳定版 -- /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope !-- 示例用H2生产环境请换为MySQL等 -- /dependency应用启动后Activiti会自动在配置的数据源中创建所需的表。接下来我们需要部署流程定义。通常我们把画好的BPMN文件放在src/main/resources/processes/目录下。可以通过代码在应用启动时自动部署Component public class ProcessDeployer { Autowired private RepositoryService repositoryService; PostConstruct public void init() { Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/leave-application.bpmn20.xml) .name(员工请假流程V1.0) .deploy(); System.out.println(流程部署成功ID: deployment.getId()); } }部署后你可以通过repositoryService.createProcessDefinitionQuery().processDefinitionKey(“leaveApplication”).latestVersion().singleResult()查询到最新的流程定义。4.2 启动流程实例与变量传递当员工在前端填写完请假单点击提交时后端服务需要启动一个流程实例。Autowired private RuntimeService runtimeService; public String startLeaveProcess(String applicantUserId, Integer leaveDays, Date startDate) { // 1. 设置流程变量 MapString, Object variables new HashMap(); variables.put(“applicant”, applicantUserId); variables.put(“leaveDays”, leaveDays); variables.put(“startDate”, startDate); // 动态查询直属领导并放入变量 variables.put(“directLeader”, userService.findDirectLeaderId(applicantUserId)); // 2. 启动流程实例 // “leaveApplication”是BPMN文件中process id“leaveApplication”定义的ID ProcessInstance instance runtimeService.startProcessInstanceByKey(“leaveApplication”, variables); // 3. 将流程实例ID与业务单据关联非常重要 leaveOrderService.bindProcessInstanceId(orderId, instance.getId()); return instance.getId(); }这里的关键是业务与流程的关联。我们必须将生成的流程实例IDinstance.getId()保存到请假业务单表中。这是后续所有操作如查询进度、处理任务的桥梁。4.3 任务查询与完成领导登录系统前端需要查询他的待办任务。Autowired private TaskService taskService; public ListTask getTasksForUser(String userId) { // 查询该用户作为处理人assignee的待办任务 ListTask tasks taskService.createTaskQuery() .taskAssignee(userId) .orderByTaskCreateTime().desc() .list(); // 通常还需要查询其作为候选组成员的任务需要先签收 ListTask candidateTasks taskService.createTaskQuery() .taskCandidateUser(userId) .list(); // 合并列表... return tasks; }领导处理任务时public void completeApprovalTask(String taskId, String approvalResult, String comment) { // 可以添加批注 taskService.addComment(taskId, null, comment); MapString, Object taskVariables new HashMap(); taskVariables.put(“approvalResult”, approvalResult); // “agree” or “reject” // 完成任务并传递决定流程走向的变量 taskService.complete(taskId, taskVariables); }当taskService.complete被调用后引擎会自动推动流程实例到下一个节点可能是另一个审批任务、网关或者服务任务。4.4 服务任务的实现服务任务代表自动执行的业务逻辑。在Activiti中最常用的方式是定义一个Java委托类。首先在BPMN XML中指定服务任务的实现类serviceTask id“deductAnnualLeave” name“扣减年假” activiti:class“com.example.flow.delegate.DeductAnnualLeaveDelegate” /然后实现这个类Component(“deductAnnualLeaveDelegate”) // 确保被Spring管理 public class DeductAnnualLeaveDelegate implements JavaDelegate { Autowired private AnnualLeaveService annualLeaveService; Override public void execute(DelegateExecution execution) { // 从流程变量中获取业务数据 String applicant (String) execution.getVariable(“applicant”); Integer leaveDays (Integer) execution.getVariable(“leaveDays”); // 执行业务逻辑 try { annualLeaveService.deduct(applicant, leaveDays); // 可以设置一个成功标志变量 execution.setVariable(“deductSuccess”, true); } catch (Exception e) { execution.setVariable(“deductSuccess”, false); // 重要抛出BpmnError可以触发边界错误事件实现优雅的错误处理 throw new BpmnError(“DEDUCT_LEAVE_ERROR”, “扣减年假失败”, e); } } }注意服务任务中的逻辑应该是幂等的。因为网络超时等原因引擎可能会重试执行。如果你的deduct方法不是幂等的可能导致年假被重复扣减。一种常见的做法是在业务单据上增加一个“已扣减”状态位或者在执行前检查流程变量中是否已有成功标记。5. 常见问题与排查技巧实录在实际开发中你一定会遇到各种“坑”。下面是我总结的一些典型问题及其排查思路。5.1 流程实例启动失败现象调用startProcessInstanceByKey后抛出异常流程实例没创建。排查步骤检查流程定义Key确认传入的processDefinitionKey与BPMN文件中process id“xxx”的id属性完全一致大小写敏感。检查部署状态去数据库ACT_RE_PROCDEF表查看流程定义是否存在、是否已部署成功。检查BPMN XML语法有些图形化设计器生成的XML可能存在格式问题。可以尝试用一个极简的流程测试。检查变量序列化如果启动时传递的变量中包含不可序列化的对象会失败。确保复杂对象实现了Serializable接口。5.2 任务查询不到现象明明流程应该走到某个用户任务了但用taskQuery查不到待办。排查步骤确认流程实例状态首先用runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult()查看流程实例是否还存在是否被意外中止或删除。确认当前活动节点使用runtimeService.getActiveActivityIds(instanceId)获取当前实例正在活动的节点ID列表。看看是否真的已经到了你期望的用户任务节点。检查任务分配人如果任务是指定了固定的assignee请确认查询时使用的userId是否完全匹配。如果是动态变量检查设置该变量的环节如流程启动或上一个任务完成时是否成功设置了正确的值。检查网关条件流程可能因为网关条件表达式评估为false而走了另一条分支根本没有到达这个用户任务节点。检查条件表达式和流程变量的值。5.3 流程“卡住”不动现象流程实例状态是ACTIVE但没有产生新的任务也不再推进。排查步骤查看历史节点查询ACT_HI_ACTINST历史活动实例表看流程最后执行到了哪个节点。这能帮你定位“卡”在哪里。检查服务任务如果卡在服务任务Service Task之前很可能是服务任务的Java委托类执行时抛出了未捕获的异常。Activiti默认会将流程实例挂起。去日志中查找错误堆栈。检查异步任务如果服务任务配置了activiti:async“true”它会进入作业队列由异步执行器处理。检查异步执行器是否启用、作业表ACT_RU_JOB中是否有失败的重试作业。检查并行网关汇聚如果使用了并行网关必须所有分支都到达汇聚点流程才会继续。检查是否有一个分支上的任务没人完成。5.4 流程变量取值为空或不对现象在某个节点读取流程变量时发现值为null或不是期望的值。排查技巧作用域混淆确认你是在正确的执行实例上获取变量。在监听器或委托类的DelegateExecution execution参数中直接用execution.getVariable()获取的是当前执行范围的变量。如果你想获取流程实例全局变量使用runtimeService.getVariable(executionId, variableName)更明确。变量生命周期任务局部变量在任务完成后会消失。如果你需要在流程全局使用某个值务必将其设置为流程实例变量。调试工具利用runtimeService.getVariables(executionId)一次性获取所有变量打印出来看看。也可以直接查询数据库表ACT_RU_VARIABLE运行时变量和ACT_HI_VARINST历史变量。5.5 历史数据查询性能慢现象随着流程实例增多查询历史记录如我发起的请假、我审批过的请假的接口响应越来越慢。优化建议分页查询Activiti的历史查询API都支持分页.listPage(start, size)前端一定要做分页。建立索引Activiti的表在初始化时可能没有为所有外键建立索引。对于经常按PROC_INST_ID_流程实例ID、START_USER_ID_发起人ID查询的历史表可以考虑添加合适的数据库索引。自定义历史表查询对于复杂的报表查询直接使用Activiti的API可能不够灵活。可以考虑将关键的业务数据如流程状态、结果、时间在流程推进时冗余存储到你自己的业务报表表中这样查询效率更高也更符合业务习惯。历史数据归档对于已经完结很久的流程实例可以考虑将其从运行时和历史表迁移到单独的归档存储中减少主表的数据量。理解并掌握流程的生命周期能让你从“画图工具使用者”转变为“流程架构师”。它帮助你提前预见开发中的难点设计出更健壮、更易维护的流程系统。下次当你面对一个BPMN图时不妨在脑海里先过一遍它的生命周期它如何被部署、如何启动、数据如何流转、任务如何分配、异常如何处置、最终又如何结束。想清楚了这些代码写起来自然就得心应手了。
从流程图到可执行代码:基于Activiti的流程引擎完整生命周期解析
1. 项目概述从流程图到可执行代码的旅程在任何一个涉及审批、流转或自动化处理的软件项目中流程引擎都是核心的“中枢神经系统”。我们经常在需求文档里看到用BPMN业务流程模型与标记法画的流程图那些圆角矩形、菱形、箭头看起来清晰明了。但很多开发者尤其是刚接触这块的朋友心里都会有个疑问这张漂亮的图到底是怎么变成系统中真正跑起来的代码的从设计到开发、测试、上线再到监控和优化这个“流程”本身经历了怎样的生命周期今天我就用一个最经典的“员工请假审批流程”作为示例带大家走一遍这个完整的旅程。我们会用到业界广泛使用的Activiti流程引擎但重点不在于某个框架的API调用而在于理解从“图”到“活系统”的完整闭环。无论你是前端、后端还是项目经理理解这个过程都能让你在涉及流程类需求时心里更有谱沟通更顺畅。2. 流程生命周期的全景透视在深入代码之前我们必须先建立起对流程生命周期的宏观认知。这绝不仅仅是“画个图部署一下”那么简单。一个健壮的流程其生命周期贯穿了项目的始终我们可以将其划分为四个核心阶段设计建模期、开发部署期、运行监控期和运维优化期。每个阶段都有其独特的关注点和产出物。2.1 设计建模期业务与技术的第一次握手这个阶段发生在任何代码编写之前是业务分析师、产品经理和架构师的主场。核心目标是将模糊的业务需求转化为精确的、可被流程引擎理解的模型。我们示例中的“员工请假审批流程”需求可能是这样的“员工提交请假申请时长小于等于3天需直属领导审批大于3天需直属领导和部门总监两级审批审批通过后系统自动扣减年假余额并通知员工审批不通过则流程结束并通知员工。”此时BPMN图就是沟通的“普通话”。我们会画出包含以下元素的流程图开始事件一个空心圆代表流程的触发点如员工点击“提交申请”按钮。用户任务圆角矩形代表需要人工参与的操作如“直属领导审批”、“部门总监审批”。网关菱形负责流程的路由决策。最常用的是排他网关它像程序中的if-else语句根据条件决定走哪条分支如判断请假天数 3。服务任务圆角矩形但内部有一个齿轮小图标代表自动执行的系统逻辑如“扣减年假余额”、“发送通知邮件”。结束事件一个粗边空心圆代表流程实例的终结。注意在画图时一个常见的误区是试图在一个流程图中涵盖所有异常情况和业务细节这会导致图形异常复杂。正确的做法是主流程清晰简洁将复杂的业务逻辑如计算请假天数是否包含节假日封装到服务任务背后的Java类或脚本中。BPMN负责描述“何时、由谁、做什么”具体的“怎么做”交给代码。2.2 开发部署期让模型“活”起来设计稿BPMN图完成后就交给了开发团队。这个阶段的目标是为静态的模型注入动态的生命使其能够在流程引擎中运行。首先我们需要将.bpmn或.bpmn20.xml文件即画好的流程图部署到流程引擎如Activiti中。部署动作不仅仅是上传一个文件引擎会做几件重要的事解析校验BPMN XML的语法和结构是否正确。持久化将流程定义的关键信息如ID、名称、版本、模型结构存入数据库通常是ACT_RE_PROCDEF表。缓存在内存中生成可执行的对象模型提升后续启动流程实例的速度。部署成功后我们就得到了一个流程定义。你可以把它理解为一个Java的Class。它定义了流程的结构但本身不会执行。当员工真正提交一张请假单时系统会基于这个“流程定义”启动一个流程实例。这就好比根据Classnew出来一个Object。这个实例拥有自己的状态、变量如applicant申请人、leaveDays请假天数、status状态并沿着流程图的路径一步步推进。每个任务用户任务或服务任务都会在数据库如ACT_RU_TASK运行时任务表中生成一条待办记录或待执行记录。2.3 运行监控期流程的“心电图”流程实例一旦启动就进入了运行期。这是生命周期中最活跃的阶段也是我们需要重点监控的阶段。对于用户任务引擎会生成待办事项。例如“直属领导审批”任务会产生一条待办出现在领导的待办列表里。领导通过前端界面点击“同意”或“拒绝”后端会调用引擎的completeTaskAPI并传递审批意见变量如approvalResult: ‘agree’。引擎接收到完成指令后会推动流程实例移动到下一个节点。对于服务任务引擎会自动执行我们预先绑定好的Java类Delegate或脚本。例如在“扣减年假余额”这个服务任务中我们绑定的Java类会从流程变量中读取applicant和leaveDays然后调用用户服务或直接操作数据库完成扣减操作。在这个过程中流程变量扮演了血液的角色在各个节点间传递数据。流程实例的状态运行中、已暂停、已结束和当前活动节点共同构成了流程的“心电图”。我们需要通过管理后台或监控API实时查看这些信息以了解业务进行到了哪一步是否有流程卡住例如某个审批任务长时间无人处理。2.4 运维优化期持续的迭代与调优流程上线并非终点。随着业务发展我们可能会发现流程节点设置不合理、审批链过长、自动化节点失败率高等问题。这时就进入了运维优化期。Activiti支持流程定义的版本管理。当你部署一个同名的、但修改过的BPMN文件时引擎会自动为其分配一个新版本号如从1.0升到2.0。默认情况下新发起的流程实例会使用最新版本而已经运行的旧实例将继续按旧版本的定义执行完毕。这实现了流程的平滑升级。此外我们还需要关注性能监控统计流程实例的平均完成时间、各节点的耗时找出瓶颈。错误处理为服务任务配置重试机制或错误边界事件避免因单个节点失败导致整个流程实例挂起。数据分析基于历史数据ACT_HI_系列表分析请假频率、审批通过率、各领导平均审批时长等为业务决策提供支持。3. 核心细节解析与实操要点理解了全景我们来深入看看几个最容易出问题的核心细节。这些地方如果理解不透彻开发时一定会踩坑。3.1 流程变量数据的载体与传递机制流程变量是流程实例的“记忆”。它本质上是一个键值对集合可以在流程启动时设置也可以在任务完成、服务任务执行时设置或修改。变量的作用域是一个关键概念流程实例变量作用域是整个流程实例在任何节点都可以存取。通常用于存储全局业务数据如applicant,leaveDays,startDate等。任务局部变量仅作用于某个特定的任务实例。任务完成后这些变量默认会消失。可用于存储临时数据但使用需谨慎。在请假流程中当领导审批时我们通常将审批意见approvalResult作为流程实例变量来传递因为后续的网关判断和通知任务都需要用到它。变量序列化是另一个要点。Activiti需要将变量值存储到数据库。对于基本类型String, Integer或实现了Serializable接口的Java对象引擎会自动处理。但对于复杂对象或频繁存取的对象直接存储可能带来性能问题。一种常见的优化模式是只将业务实体的ID如leaveOrderId作为流程变量存储在需要具体数据时再通过这个ID去查询业务数据库。3.2 网关流程的决策大脑网关控制着流程的走向。最常用的是排他网关和并行网关。排他网关用于“多选一”的场景。在请假流程中我们用它来判断请假天数。在网关的每条流出顺序流上都需要设置一个条件表达式通常使用UELUnified Expression Language来编写例如${leaveDays 3}。引擎会按顺序流定义的顺序评估条件第一条评估为true的路径将被执行。务必设置一条默认流不设条件作为所有条件都不满足时的出口避免流程卡死。并行网关用于“所有路径都必须执行”的场景比如需要同时通知申请人和HR。它不计算条件所有流出路径都会同时产生新的执行分支。所有分支都完成后才会汇聚到下一个节点。使用并行网关时要特别注意任务签收和完成的一致性避免因某个分支的任务无人处理而导致整个流程实例无法继续。3.3 任务分配明确“谁来做”用户任务必须明确指定处理人Assignee或候选组Candidate Group。指定方式有多种固定人员在BPMN图中直接写死activiti:assignee“zhangsan”。这种方式极不灵活仅用于演示或固定角色的场景。通过变量动态指定这是最常用的方式。例如activiti:assignee“${directLeader}”。在流程启动前或启动时我们需要将directLeader直属领导ID这个变量设置好。通过监听器指定创建一个任务创建监听器TaskListener在EVENTNAME_CREATE事件中编写复杂的逻辑来计算处理人例如从组织架构服务中查询当前申请人的上级领导。实操心得我强烈推荐使用“候选组”而非直接指定“处理人”。例如设置activiti:candidateGroups“dept:manager”。这样所有属于“dept:manager”这个组的用户都能看到这个任务并可以“签收”它使之成为自己的待办。这带来了灵活性领导出差时组内其他成员可以代为处理。前端界面则需要提供“签收任务”和“查询候选组任务”的功能。4. 实操过程与核心环节实现下面我们以Spring Boot集成Activiti 7为例看看如何将上述理论落地。这里不会罗列所有代码而是聚焦于几个最关键的操作。4.1 环境准备与流程部署首先在pom.xml中引入依赖。Activiti 7提供了Spring Boot Starter大大简化了配置。dependency groupIdorg.activiti/groupId artifactIdactiviti-spring-boot-starter/artifactId version7.1.0.M6/version !-- 请使用最新稳定版 -- /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope !-- 示例用H2生产环境请换为MySQL等 -- /dependency应用启动后Activiti会自动在配置的数据源中创建所需的表。接下来我们需要部署流程定义。通常我们把画好的BPMN文件放在src/main/resources/processes/目录下。可以通过代码在应用启动时自动部署Component public class ProcessDeployer { Autowired private RepositoryService repositoryService; PostConstruct public void init() { Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/leave-application.bpmn20.xml) .name(员工请假流程V1.0) .deploy(); System.out.println(流程部署成功ID: deployment.getId()); } }部署后你可以通过repositoryService.createProcessDefinitionQuery().processDefinitionKey(“leaveApplication”).latestVersion().singleResult()查询到最新的流程定义。4.2 启动流程实例与变量传递当员工在前端填写完请假单点击提交时后端服务需要启动一个流程实例。Autowired private RuntimeService runtimeService; public String startLeaveProcess(String applicantUserId, Integer leaveDays, Date startDate) { // 1. 设置流程变量 MapString, Object variables new HashMap(); variables.put(“applicant”, applicantUserId); variables.put(“leaveDays”, leaveDays); variables.put(“startDate”, startDate); // 动态查询直属领导并放入变量 variables.put(“directLeader”, userService.findDirectLeaderId(applicantUserId)); // 2. 启动流程实例 // “leaveApplication”是BPMN文件中process id“leaveApplication”定义的ID ProcessInstance instance runtimeService.startProcessInstanceByKey(“leaveApplication”, variables); // 3. 将流程实例ID与业务单据关联非常重要 leaveOrderService.bindProcessInstanceId(orderId, instance.getId()); return instance.getId(); }这里的关键是业务与流程的关联。我们必须将生成的流程实例IDinstance.getId()保存到请假业务单表中。这是后续所有操作如查询进度、处理任务的桥梁。4.3 任务查询与完成领导登录系统前端需要查询他的待办任务。Autowired private TaskService taskService; public ListTask getTasksForUser(String userId) { // 查询该用户作为处理人assignee的待办任务 ListTask tasks taskService.createTaskQuery() .taskAssignee(userId) .orderByTaskCreateTime().desc() .list(); // 通常还需要查询其作为候选组成员的任务需要先签收 ListTask candidateTasks taskService.createTaskQuery() .taskCandidateUser(userId) .list(); // 合并列表... return tasks; }领导处理任务时public void completeApprovalTask(String taskId, String approvalResult, String comment) { // 可以添加批注 taskService.addComment(taskId, null, comment); MapString, Object taskVariables new HashMap(); taskVariables.put(“approvalResult”, approvalResult); // “agree” or “reject” // 完成任务并传递决定流程走向的变量 taskService.complete(taskId, taskVariables); }当taskService.complete被调用后引擎会自动推动流程实例到下一个节点可能是另一个审批任务、网关或者服务任务。4.4 服务任务的实现服务任务代表自动执行的业务逻辑。在Activiti中最常用的方式是定义一个Java委托类。首先在BPMN XML中指定服务任务的实现类serviceTask id“deductAnnualLeave” name“扣减年假” activiti:class“com.example.flow.delegate.DeductAnnualLeaveDelegate” /然后实现这个类Component(“deductAnnualLeaveDelegate”) // 确保被Spring管理 public class DeductAnnualLeaveDelegate implements JavaDelegate { Autowired private AnnualLeaveService annualLeaveService; Override public void execute(DelegateExecution execution) { // 从流程变量中获取业务数据 String applicant (String) execution.getVariable(“applicant”); Integer leaveDays (Integer) execution.getVariable(“leaveDays”); // 执行业务逻辑 try { annualLeaveService.deduct(applicant, leaveDays); // 可以设置一个成功标志变量 execution.setVariable(“deductSuccess”, true); } catch (Exception e) { execution.setVariable(“deductSuccess”, false); // 重要抛出BpmnError可以触发边界错误事件实现优雅的错误处理 throw new BpmnError(“DEDUCT_LEAVE_ERROR”, “扣减年假失败”, e); } } }注意服务任务中的逻辑应该是幂等的。因为网络超时等原因引擎可能会重试执行。如果你的deduct方法不是幂等的可能导致年假被重复扣减。一种常见的做法是在业务单据上增加一个“已扣减”状态位或者在执行前检查流程变量中是否已有成功标记。5. 常见问题与排查技巧实录在实际开发中你一定会遇到各种“坑”。下面是我总结的一些典型问题及其排查思路。5.1 流程实例启动失败现象调用startProcessInstanceByKey后抛出异常流程实例没创建。排查步骤检查流程定义Key确认传入的processDefinitionKey与BPMN文件中process id“xxx”的id属性完全一致大小写敏感。检查部署状态去数据库ACT_RE_PROCDEF表查看流程定义是否存在、是否已部署成功。检查BPMN XML语法有些图形化设计器生成的XML可能存在格式问题。可以尝试用一个极简的流程测试。检查变量序列化如果启动时传递的变量中包含不可序列化的对象会失败。确保复杂对象实现了Serializable接口。5.2 任务查询不到现象明明流程应该走到某个用户任务了但用taskQuery查不到待办。排查步骤确认流程实例状态首先用runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult()查看流程实例是否还存在是否被意外中止或删除。确认当前活动节点使用runtimeService.getActiveActivityIds(instanceId)获取当前实例正在活动的节点ID列表。看看是否真的已经到了你期望的用户任务节点。检查任务分配人如果任务是指定了固定的assignee请确认查询时使用的userId是否完全匹配。如果是动态变量检查设置该变量的环节如流程启动或上一个任务完成时是否成功设置了正确的值。检查网关条件流程可能因为网关条件表达式评估为false而走了另一条分支根本没有到达这个用户任务节点。检查条件表达式和流程变量的值。5.3 流程“卡住”不动现象流程实例状态是ACTIVE但没有产生新的任务也不再推进。排查步骤查看历史节点查询ACT_HI_ACTINST历史活动实例表看流程最后执行到了哪个节点。这能帮你定位“卡”在哪里。检查服务任务如果卡在服务任务Service Task之前很可能是服务任务的Java委托类执行时抛出了未捕获的异常。Activiti默认会将流程实例挂起。去日志中查找错误堆栈。检查异步任务如果服务任务配置了activiti:async“true”它会进入作业队列由异步执行器处理。检查异步执行器是否启用、作业表ACT_RU_JOB中是否有失败的重试作业。检查并行网关汇聚如果使用了并行网关必须所有分支都到达汇聚点流程才会继续。检查是否有一个分支上的任务没人完成。5.4 流程变量取值为空或不对现象在某个节点读取流程变量时发现值为null或不是期望的值。排查技巧作用域混淆确认你是在正确的执行实例上获取变量。在监听器或委托类的DelegateExecution execution参数中直接用execution.getVariable()获取的是当前执行范围的变量。如果你想获取流程实例全局变量使用runtimeService.getVariable(executionId, variableName)更明确。变量生命周期任务局部变量在任务完成后会消失。如果你需要在流程全局使用某个值务必将其设置为流程实例变量。调试工具利用runtimeService.getVariables(executionId)一次性获取所有变量打印出来看看。也可以直接查询数据库表ACT_RU_VARIABLE运行时变量和ACT_HI_VARINST历史变量。5.5 历史数据查询性能慢现象随着流程实例增多查询历史记录如我发起的请假、我审批过的请假的接口响应越来越慢。优化建议分页查询Activiti的历史查询API都支持分页.listPage(start, size)前端一定要做分页。建立索引Activiti的表在初始化时可能没有为所有外键建立索引。对于经常按PROC_INST_ID_流程实例ID、START_USER_ID_发起人ID查询的历史表可以考虑添加合适的数据库索引。自定义历史表查询对于复杂的报表查询直接使用Activiti的API可能不够灵活。可以考虑将关键的业务数据如流程状态、结果、时间在流程推进时冗余存储到你自己的业务报表表中这样查询效率更高也更符合业务习惯。历史数据归档对于已经完结很久的流程实例可以考虑将其从运行时和历史表迁移到单独的归档存储中减少主表的数据量。理解并掌握流程的生命周期能让你从“画图工具使用者”转变为“流程架构师”。它帮助你提前预见开发中的难点设计出更健壮、更易维护的流程系统。下次当你面对一个BPMN图时不妨在脑海里先过一遍它的生命周期它如何被部署、如何启动、数据如何流转、任务如何分配、异常如何处置、最终又如何结束。想清楚了这些代码写起来自然就得心应手了。