Activiti7实战指南:从流程实例到任务分配的深度解析

Activiti7实战指南:从流程实例到任务分配的深度解析 1. Activiti7流程实例全生命周期管理Activiti7作为业界领先的工作流引擎其核心价值在于对业务流程的精确控制。流程实例ProcessInstance是流程定义的具体执行载体理解它的生命周期对开发高效业务流程至关重要。想象一下当员工提交出差申请时系统就会创建一个流程实例这个实例会按照预定义的流程节点逐步推进。启动流程实例时有个关键技巧——BusinessKey的妙用。这个业务标识相当于给流程实例打上业务标签通常使用业务表的主键。比如出差流程中我们可以把出差单ID作为BusinessKey这样就能轻松关联业务数据Test public void startProcessWithBusinessKey() { ProcessEngine engine ProcessEngines.getDefaultProcessEngine(); RuntimeService runtimeService engine.getRuntimeService(); // 启动流程实例并关联业务单据ID ProcessInstance instance runtimeService .startProcessInstanceByKey(travelProcess, T20230001); System.out.println(关联的业务ID instance.getBusinessKey()); }在实际项目中我经常遇到需要动态控制流程的场景。比如疫情期间我们可能需要批量暂停所有出差流程。Activiti7提供了完善的挂起/激活机制// 挂起单个流程实例 runtimeService.suspendProcessInstanceById(instanceId); // 激活整个流程定义影响所有实例 repositoryService.activateProcessDefinitionById(processDefinitionId);重要提示流程实例挂起后尝试执行任务会抛出ActivitiException。在代码中需要做好状态判断ProcessInstance instance runtimeService .createProcessInstanceQuery() .processInstanceId(instanceId) .singleResult(); if(instance.isSuspended()) { throw new BusinessException(流程已暂停无法操作); }2. 任务分配的三板斧2.1 固定分配简单但不够灵活在BPMN设计器中直接指定处理人是最简单的方式适合审批角色固定的场景。比如财务审批节点我们可以在属性面板直接设置assignee为finance_auditor。但这种方式硬编码严重我在电商项目中就吃过亏——当组织架构调整时不得不修改所有相关流程定义。建议仅在处理角色极其稳定的场景使用。2.2 表达式分配动态灵活的实践UEL表达式让任务分配活了起来。最常用的有两种写法直接引用变量${approver}调用对象方法${userService.getManager(employee)}在出差审批流程中我们可以这样动态设置审批人// 启动流程时注入变量 MapString, Object variables new HashMap(); variables.put(departmentAuditor, 王经理); variables.put(financeAuditor, 张会计); runtimeService.startProcessInstanceByKey(travelProcess, variables);踩坑提醒表达式中的变量必须存在否则会抛出异常。稳妥的做法是设置默认值userTask idauditTask name部门审批 activiti:assignee${departmentAuditor?:default_auditor} /2.3 监听器分配最强大的分配方式TaskListener让我们可以在运行时动态决定处理人。我曾用这种方式实现过复杂的会签逻辑public class DynamicAssigneeListener implements TaskListener { Override public void notify(DelegateTask task) { String eventName task.getEventName(); if (create.equals(eventName)) { String processType (String) task.getVariable(processType); // 根据流程类型分配不同负责人 if(VIP.equals(processType)){ task.setAssignee(VIP_Manager); } else { task.setAssignee(Normal_Manager); } } } }在BPMN中配置监听器时记得选择正确的事件类型create/assignment/complete等。我曾经因为选错事件类型导致监听器不执行排查了半天。3. 流程变量的艺术3.1 全局变量与局部变量全局变量(Global)在整个流程实例中可见而局部变量(Local)只在当前任务或执行中有效。在电商退货流程中我这样使用它们// 设置全局退货原因 runtimeService.setVariable(executionId, returnReason, 商品破损); // 设置质检任务的局部变量 taskService.setVariableLocal(taskId, qualityScore, 90);经验之谈涉及业务决策的关键数据如审批金额建议用全局变量而任务临时数据如处理意见适合用局部变量。3.2 变量序列化要点当需要存储复杂对象时必须实现Serializable接口。我推荐使用阿里fastjson辅助序列化public class Order implements Serializable { private static final long serialVersionUID 1L; JSONField(name order_no) private String orderNo; // getters/setters }血泪教训新增字段时要考虑反序列化兼容性。有次新增字段导致历史流程无法加载最后通过自定义序列化方案解决。3.3 变量使用最佳实践启动时注入流程需要的初始数据尽量在启动时设置避免大对象超过10KB的对象建议只存ID命名规范化使用驼峰命名如approvalComment及时清理流程结束后通过runtimeService.deleteVariable()清理敏感数据在金融项目中我们这样安全地使用变量// 加密存储敏感信息 variables.put(idNumber, encrypt(user.getIdNumber())); // 启动流程 ProcessInstance instance runtimeService.startProcessInstanceByKey( loanProcess, businessKey, variables ); // 立即清理内存中的明文数据 variables.clear();4. 组任务与网关实战4.1 组任务的高效管理候选人模式(candidateUsers)非常适合需要多人协作的场景。比如采购审批流程userTask idbidReview name投标评审 activiti:candidateUsersreviewer1,reviewer2,reviewer3/代码中实现任务拾取与归还// 拾取任务 taskService.claim(taskId, userId); // 归还任务 taskService.setAssignee(taskId, null); // 任务转交 taskService.setAssignee(taskId, newAssignee);性能提示候选人列表不宜过长超过20人建议改用候选组(candidateGroups)。4.2 网关选型指南排他网关用于单一条件分支如${days 3}并行网关用于会签场景所有分支必须执行包含网关条件性并行如同时满足${needHR} ${needFinance}在报销流程中我这样设计包含网关inclusiveGateway idinclusiveGateway1 / sequenceFlow sourceRefinclusiveGateway1 targetRefhrAudit conditionExpression xsi:typetFormalExpression ${amount 5000} /conditionExpression /sequenceFlow sequenceFlow sourceRefinclusiveGateway1 targetReffinanceAudit conditionExpression xsi:typetFormalExpression ${needSpecialApprove} /conditionExpression /sequenceFlow排查技巧当网关出现异常时首先检查所有出口条件是否完整流程变量是否存在且类型正确条件表达式语法是否正确5. 性能优化与常见陷阱经过多个百万级流程的项目实践我总结出这些优化要点查询优化// 不好的写法 ListTask tasks taskService.createTaskQuery() .list(); // 好的写法 - 分页过滤 ListTask tasks taskService.createTaskQuery() .taskCandidateOrAssigned(userId) .processDefinitionKey(travelProcess) .orderByTaskCreateTime().desc() .listPage(0, 50);批量操作使用signalEventReceived触发批量流程历史数据定期清理ACT_HI_*表保留最近3个月数据典型陷阱在循环中频繁查询数据库应改用批量查询过度使用流程变量导致序列化开销忽略事务边界长时间运行的任务要拆分最后分享一个真实案例某次上线后流程突然变慢最终发现是监听器中同步调用了外部HTTP接口。改为异步处理本地缓存后性能提升10倍。