别再硬编码了!Flowable流程节点信息动态获取的完整配置流程
告别硬编码:Flowable动态流程节点配置实战指南
在电商订单审批这类业务场景中,我们经常遇到这样的困境:不同金额的订单需要走不同的审批路径,开发人员不得不在代码中写满if-else逻辑。这种硬编码方式不仅让流程变得僵化,每次业务规则变更都需要重新发布代码,更让系统维护成本居高不下。本文将带你深入Flowable的动态流程配置能力,通过BpmnModel API和运行时表达式,实现审批路径的完全动态化。
1. 动态流程设计的核心思想
传统流程设计中,节点跳转规则和候选人列表往往直接编码在流程定义或Java代码中。这种做法的弊端显而易见:
- 业务规则变更需要重新部署:每次调整审批金额阈值或审批人,都需要修改流程定义或代码
- 代码与流程模型强耦合:流程逻辑分散在BPMN文件和代码中,难以维护
- 缺乏灵活性:无法根据运行时数据动态决定审批路径
Flowable提供的动态流程能力,让我们可以将这些决策点从代码中解放出来,转而通过流程变量和表达式在运行时动态决定。这种架构的核心优势在于:
- 配置化:审批规则可以通过数据库或配置文件管理,无需修改代码
- 可维护性:业务人员可以自行调整规则,减少对开发团队的依赖
- 扩展性:新的审批场景可以通过配置实现,无需额外开发
2. 动态节点探测技术实现
2.1 获取当前流程上下文
要动态决定下一个节点,首先需要获取当前流程的完整上下文信息。以下代码展示了如何通过任务ID获取流程实例和BPMN模型:
public FlowNodeInfo getCurrentNodeInfo(String taskId) { // 获取当前任务 Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); // 获取流程实例和定义信息 ProcessInstance instance = runtimeService.createProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); // 获取BPMN模型 BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId()); // 获取当前节点 FlowElement currentElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); return new FlowNodeInfo(currentElement, instance.getVariables()); }2.2 解析出站连线与条件表达式
每个流程节点的出站连线(SequenceFlow)包含了流向下一节点的条件。我们可以通过分析这些条件表达式,结合当前流程变量,动态决定下一步走向:
public List<NextNodeCandidate> evaluateOutgoingFlows(FlowNode currentNode, Map<String, Object> variables) { List<NextNodeCandidate> candidates = new ArrayList<>(); for (SequenceFlow flow : currentNode.getOutgoingFlows()) { if (flow.getConditionExpression() == null) { // 无条件连线,默认路径 candidates.add(new NextNodeCandidate(flow.getTargetFlowElement(), true)); } else { // 有条件连线,评估表达式 boolean result = (boolean) runtimeService.evaluateCondition( flow.getConditionExpression(), variables ); candidates.add(new NextNodeCandidate(flow.getTargetFlowElement(), result)); } } return candidates; }2.3 动态候选人列表配置
传统做法中,任务候选人往往硬编码在流程定义中。我们可以通过以下方式实现动态候选人配置:
- 表达式赋值:在BPMN中使用
${approvalService.findApprovers(task)}这样的表达式 - 监听器注入:通过TaskListener在任务创建时动态设置候选人
- 外部存储查询:将审批规则存储在数据库或配置中心
public class DynamicApproverService { public List<String> findApprovers(Task task) { // 从外部系统或数据库查询审批规则 ApprovalRule rule = ruleRepository.findByProcessDefAndTask( task.getProcessDefinitionId(), task.getTaskDefinitionKey() ); // 根据业务数据应用规则 Map<String, Object> variables = runtimeService.getVariables(task.getExecutionId()); return rule.apply(variables); } }3. 电商订单审批实战案例
让我们以一个电商订单审批流程为例,展示如何将硬编码的审批规则转化为动态配置。
3.1 传统硬编码实现的问题
原始实现可能如下所示:
if (orderAmount < 1000) { // 直接自动通过 runtimeService.setVariable(executionId, "approved", true); } else if (orderAmount < 5000) { // 需要部门经理审批 taskService.addCandidateUser(taskId, "dept_manager"); } else { // 需要财务总监审批 taskService.addCandidateUser(taskId, "finance_director"); }这种实现方式存在明显问题:
- 审批金额阈值修改需要重新发布代码
- 审批人变更需要修改流程定义
- 无法根据不同产品类型设置不同规则
3.2 动态配置改造方案
我们可以将审批规则外置到数据库表中:
| 规则ID | 流程定义Key | 节点Key | 条件表达式 | 审批人表达式 |
|---|---|---|---|---|
| 1 | orderApproval | approve1 | ${amount < 1000} | ${autoApprover} |
| 2 | orderApproval | approve1 | ${amount >= 1000 && amount < 5000} | ${approvalService.findDeptManagers(deptId)} |
| 3 | orderApproval | approve1 | ${amount >= 5000} | ${approvalService.findFinanceDirectors()} |
然后在流程中使用通用表达式:
<sequenceFlow id="flow1" sourceRef="approve1" targetRef="approve2"> <conditionExpression xsi:type="tFormalExpression"> ${approvalService.evaluateRule(execution, 'approve1')} </conditionExpression> </sequenceFlow>3.3 网关节点的动态路由
对于复杂的审批路由,我们可以利用排他网关(Exclusive Gateway)实现动态分支:
public void evaluateGateway(FlowElement gateway, Map<String, Object> variables) { if (!(gateway instanceof ExclusiveGateway)) return; ExclusiveGateway exclusiveGateway = (ExclusiveGateway) gateway; for (SequenceFlow flow : exclusiveGateway.getOutgoingFlows()) { boolean conditionMet = true; // 默认路径 if (flow.getConditionExpression() != null) { conditionMet = (boolean) runtimeService.evaluateCondition( flow.getConditionExpression(), variables ); } if (conditionMet) { // 这是符合条件的路径 FlowElement nextElement = flow.getTargetFlowElement(); if (nextElement instanceof UserTask) { setupDynamicUserTask((UserTask) nextElement, variables); } break; } } }4. 高级动态配置技巧
4.1 会签节点的动态参与者
会签(Multi-Instance)任务通常需要动态确定参与者列表。我们可以通过以下方式实现:
<userTask id="reviewTask" name="会签评审"> <multiInstanceLoopCharacteristics isSequential="false" collection="${approvalService.findReviewers(execution)}" elementVariable="reviewer"> </multiInstanceLoopCharacteristics> <potentialOwner> <resourceAssignmentExpression> <formalExpression>${reviewer}</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask>4.2 基于角色的动态任务分配
结合企业组织结构,我们可以实现基于角色的动态任务分配:
public class RoleBasedAssignmentHandler implements TaskAssignmentHandler { public void handleAssignment(Task task, Map<String, Object> variables) { String role = (String) variables.get("requiredApprovalRole"); List<String> users = roleService.findUsersByRole(role); for (String userId : users) { taskService.addCandidateUser(task.getId(), userId); } } }4.3 流程变量的动态注入
有时我们需要在流程执行过程中动态注入变量:
public class DynamicVariableInjectionListener implements ExecutionListener { public void notify(DelegateExecution execution) { Map<String, Object> dynamicVars = variableService .fetchDynamicVariables(execution); execution.setVariables(dynamicVars); } }5. 性能优化与最佳实践
动态流程虽然灵活,但也可能带来性能开销。以下是几个优化建议:
缓存BPMN模型:避免频繁查询数据库获取BPMN定义
@Cacheable(value = "bpmnModels", key = "#processDefinitionId") public BpmnModel getBpmnModel(String processDefinitionId) { return repositoryService.getBpmnModel(processDefinitionId); }预编译条件表达式:对频繁评估的表达式进行预编译
private static final Map<String, Expression> expressionCache = new ConcurrentHashMap<>(); public boolean evaluateCondition(String expression, Map<String, Object> variables) { Expression compiledExpr = expressionCache.computeIfAbsent( expression, expr -> runtimeService.createExpression(expr) ); return (boolean) compiledExpr.getValue(variables); }批量查询审批规则:避免为每个任务单独查询数据库
异步处理非关键路径:对于不直接影响流程走向的操作,可以采用异步方式
在实际项目中,我们通过上述动态流程配置方案,将电商订单审批流程的变更发布频率从每月2-3次降低到几乎为零,业务部门可以自行在管理后台调整审批规则,大大提升了系统的灵活性和可维护性。
