• Camunda自定义多实例审批人列表


    Camunda自定义多实例审批人列表

    1.多实例案例

    工作流中会有遇到这样一个"多个人处理同一个任务“的情形,在 camunda 中可以使用"任务的多实例"来实现。

    这里以或签为例,可以设置完成条件为 ${nrOfCompletedInstances==1},如果是会签,设置成 ${nrOfCompletedInstances==n}即可

    image-20230921213113920

    bpmn核心配置为

     <bpmn:userTask id="Activity_0aal2tp" name="或签" camunda:assignee="${assignee}">
         <bpmn:incoming>Flow_10mm1vybpmn:incoming>
         <bpmn:outgoing>Flow_1e19xopbpmn:outgoing>
         <bpmn:multiInstanceLoopCharacteristics camunda:collection="assigneeList" camunda:elementVariable="assignee">
             <bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances==1}bpmn:completionCondition>
         bpmn:multiInstanceLoopCharacteristics>
    bpmn:userTask>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.源码走读

    源码环境:camunda-bpm-spring-boot-starter-7.17.0.jar

    2.1 启动类

    打开 spring.factories ,可以看到自动注类为 org.camunda.bpm.spring.boot.starter.CamundaBpmAutoConfiguration

    image-20230921214448879

    进入该类,可以看到默认的处理引擎类为 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl

    @Autowired
    protected ProcessEngineConfigurationImpl processEngineConfigurationImpl;
    
    @Bean
    public ProcessEngineFactoryBean processEngineFactoryBean() {
        final ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean();
        factoryBean.setProcessEngineConfiguration(processEngineConfigurationImpl);
    
        return factoryBean;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ProcessEngineFactoryBeanFactoryBean,核心方法为 getObject

    public ProcessEngine getObject() throws Exception {
        if (processEngine == null) {
            initializeExpressionManager();
            initializeTransactionExternallyManaged();
    
            processEngine = (ProcessEngineImpl) processEngineConfiguration.buildProcessEngine();
        }
    
        return processEngine;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最终执行 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImplbuildProcessEngine() 方法

    @Override
    public ProcessEngine buildProcessEngine() {
        // 初始化各个组件
        init();
        processEngine = new ProcessEngineImpl(this);
        invokePostProcessEngineBuild(processEngine);
        return processEngine;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最终会走到 getBpmnDeployer() 方法,梳理一下前面的调用链

    CamundaBpmAutoConfiguration#processEngineFactoryBean()
      ProcessEngineFactoryBean#getObject()
        ProcessEngineConfigurationImpl#buildProcessEngine()
          ProcessEngineConfigurationImpl#init()
            ProcessEngineConfigurationImpl#initDeployers()
              ProcessEngineConfigurationImpl#getDefaultDeployers()
                ProcessEngineConfigurationImpl#getBpmnDeployer()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    进而获取解析核心工厂类 DefaultBpmnParseFactory

    if (bpmnParseFactory == null) {
        bpmnParseFactory = new DefaultBpmnParseFactory();
    }
    
    • 1
    • 2
    • 3

    该工厂类中指定了获取 BpmnParse 的方法

    public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
        return new BpmnParse(bpmnParser);
    }
    
    • 1
    • 2
    • 3

    BpmnParse是解析流程图的核心类

    2.2 获取审批人的方法

    部署一下该流程

    @Test
    public void testDeploy() {
        Deployment deploy = repositoryService
            .createDeployment()
            .name("多实例案例")
            .tenantId("zyx")
            .addClasspathResource("bpmn/midemo.bpmn")
            .deploy();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后发起流程

    @ParameterizedTest
    @ValueSource(strings = {"1704859505474060288"})
    public void testStartMultiInstance(String deployId) {
        ProcessDefinition processDef = repositoryService
            .createProcessDefinitionQuery()
            .deploymentId(deployId)
            .tenantIdIn("zyx")
            .singleResult();
        VariableMap variableMap = Variables.createVariables()
            .putValue("assigneeList", Arrays.asList("201", "202", "203"));
        ProcessInstance processInstance = runtimeService
            .startProcessInstanceById(processDef.getId(), IdUtil.getSnowflakeNextIdStr(), variableMap);
        // log.info("==========>>>>>>>>>> instance id: {}", processInstance.getId());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    重点关注 BpmnParse#parseActivity(Element activityElement, Element parentElement, ScopeImpl scopeElement)

    关注 userTask 事件,debug进入 parseMultiInstanceLoopCharacteristics 方法

    image-20230921221703930

    核心代码如下,可以与前面的配置文件搭配查看

    public ScopeImpl parseMultiInstanceLoopCharacteristics(Element activityElement, ScopeImpl scope) {
    
        Element miLoopCharacteristics = activityElement.element("multiInstanceLoopCharacteristics");
        if (miLoopCharacteristics == null) {
            return null;
        } else {
            String id = activityElement.attribute("id");
    
            LOG.parsingElement("mi body for activity", id);
    
            id = getIdForMiBody(id);
            ActivityImpl miBodyScope = scope.createActivity(id);
            setActivityAsyncDelegates(miBodyScope);
            miBodyScope.setProperty(PROPERTYNAME_TYPE, ActivityTypes.MULTI_INSTANCE_BODY);
            miBodyScope.setScope(true);
    
            boolean isSequential = parseBooleanAttribute(miLoopCharacteristics.attribute("isSequential"), false);
    
            MultiInstanceActivityBehavior behavior = null;
            if (isSequential) {
                behavior = new SequentialMultiInstanceActivityBehavior();
            } else {
                behavior = new ParallelMultiInstanceActivityBehavior();
            }
            miBodyScope.setActivityBehavior(behavior);
    
            // loopCardinality
            Element loopCardinality = miLoopCharacteristics.element("loopCardinality");
            if (loopCardinality != null) {
                String loopCardinalityText = loopCardinality.getText();
                if (loopCardinalityText == null || "".equals(loopCardinalityText)) {
                    addError("loopCardinality must be defined for a multiInstanceLoopCharacteristics definition ", miLoopCharacteristics, id);
                }
                behavior.setLoopCardinalityExpression(expressionManager.createExpression(loopCardinalityText));
            }
    		// 指定终止条件
            Element completionCondition = miLoopCharacteristics.element("completionCondition");
            if (completionCondition != null) {
                String completionConditionText = completionCondition.getText();
                behavior.setCompletionConditionExpression(expressionManager.createExpression(completionConditionText));
            }
    		// 获取 collection 的名称
            String collection = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "collection");
            if (collection != null) {
                if (collection.contains("{")) {
                    behavior.setCollectionExpression(expressionManager.createExpression(collection));
                } else {
                    behavior.setCollectionVariable(collection);
                }
            }
    
            // ............
            return miBodyScope;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    可以看到已经获取了 bpmn 中多实例相关参数

    image-20230921223652525

    多实例的执行位于 MultiInstanceActivityBehavior#execute

    @Override
    public void execute(ActivityExecution execution) throws Exception {
        int nrOfInstances = resolveNrOfInstances(execution);
        if (nrOfInstances == 0) {
            leave(execution);
        }
        else if (nrOfInstances < 0) {
            throw LOG.invalidAmountException("instances", nrOfInstances);
        }
        else {
            createInstances(execution, nrOfInstances);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    跟进到 resolveNrOfInstances 方法,可以看到 nrOfInstances 本质还是获取参数中的 list 的大小

    protected int resolveNrOfInstances(ActivityExecution execution) {
        int nrOfInstances = -1;
        if (loopCardinalityExpression != null) {
            nrOfInstances = resolveLoopCardinality(execution);
        } else if (collectionExpression != null) {
            Object obj = collectionExpression.getValue(execution);
            if (!(obj instanceof Collection)) {
                throw LOG.unresolvableExpressionException(collectionExpression.getExpressionText(), "Collection");
            }
            nrOfInstances = ((Collection<?>) obj).size();
        } else if (collectionVariable != null) {
            Object obj = execution.getVariable(collectionVariable);
            if (!(obj instanceof Collection)) {
                throw LOG.invalidVariableTypeException(collectionVariable, "Collection");
            }
            nrOfInstances = ((Collection<?>) obj).size();
        } else {
            throw LOG.resolveCollectionExpressionOrVariableReferenceException();
        }
        return nrOfInstances;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后 debug 到 MultiInstanceActivityBehavior#createInstances 可以看到是一个抽象方法

    执行类为 ParallelMultiInstanceActivityBehavior,这个在前面看到过,是在 BpmnParse#parseMultiInstanceLoopCharacteristics 方法中定义的

    image-20230921225805896

    ParallelMultiInstanceActivityBehaviorcreateConcurrentExecution(execution) 创建 execution并添加到 concurrentExecutions

    然后从 concurrentExecutions 中逐一获取,通过 MultiInstanceActivityBehavior#performInstance 填充属性,这一步会通过索引及列表填充审批人的参数

    protected void performInstance(ActivityExecution execution, PvmActivity activity, int loopCounter) {
        setLoopVariable(execution, LOOP_COUNTER, loopCounter);
        // 填充列表参数
        evaluateCollectionVariable(execution, loopCounter);
        execution.setEnded(false);
        execution.setActive(true);
        execution.executeActivity(activity);
    }
    
    protected void evaluateCollectionVariable(ActivityExecution execution, int loopCounter) {
        if (usesCollection() && collectionElementVariable != null) {
            Collection<?> collection = null;
            if (collectionExpression != null) {
                collection = (Collection<?>) collectionExpression.getValue(execution);
            } else if (collectionVariable != null) {
                collection = (Collection<?>) execution.getVariable(collectionVariable);
            }
    		// 通过列表及索引获取实际参数
            Object value = getElementAtIndex(loopCounter, collection);
            setLoopVariable(execution, collectionElementVariable, value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    2.3 小结

    通过上面的源码阅读发现,指定审批人参数的核心方法有两个:

    • MultiInstanceActivityBehavior#nrOfInstances :获取审批人个数
    • MultiInstanceActivityBehavior#evaluateCollectionVariable :计算审批人属性

    其实最最核心的还是上面一个方法,因为它先执行,我们甚至可以直接在里面指定审批人列表 ,而且它决定了审批人的个数,会影响后面人员的获取

    3.自定义审批人实现

    通过上面的分析,我们可以看到,要实现自定义获取审批人列表,可以通过实现下面两个方法中的一个:

    • MultiInstanceActivityBehavior#nrOfInstances :直接修改审批人列表参数
    • MultiInstanceActivityBehavior#evaluateCollectionVariable :计算审批人属性时按照自定义方法获取

    修改默认方法可以参考下面的链接:

    camunda spirngboot配置

    主要是需要添加一个 ProcessEnginePlugin,里面可以设置 BpmnParseFactory

    • MyCamundaConfiguration
    @Configuration
    public class MyCamundaConfiguration {
    
        @Bean
        @Order(Ordering.DEFAULT_ORDER + 1)
        public static ProcessEnginePlugin myCustomConfiguration() {
            return new MyCustomConfiguration();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • MyCustomConfiguration
    public class MyCustomConfiguration implements ProcessEnginePlugin {
        @Override
        public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
            // 自定义 BpmnParse 工厂
            processEngineConfiguration.setBpmnParseFactory(new CustomBpmnParseFactory());
        }
    
        @Override
        public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
        }
    
        @Override
        public void postProcessEngineBuild(ProcessEngine processEngine) {
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • CustomBpmnParseFactory
    public class CustomBpmnParseFactory extends DefaultBpmnParseFactory {
    
        @Override
        public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
            return new CustomeBpmnParse(bpmnParser);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • CustomeBpmnParse
    public class CustomeBpmnParse extends BpmnParse {
    
        public CustomeBpmnParse(BpmnParser parser) {
            super(parser);
        }
    
        /**
         * 自定义解析多实例流程
         */
        @Override
        public ScopeImpl parseMultiInstanceLoopCharacteristics(Element activityElement, ScopeImpl scope) {
            Element miLoopCharacteristics = activityElement.element("multiInstanceLoopCharacteristics");
            if (miLoopCharacteristics == null) {
                return null;
            } else {
                String id = activityElement.attribute("id");
                LOG.parsingElement("mi body for activity", id);
                id = getIdForMiBody(id);
                ActivityImpl miBodyScope = scope.createActivity(id);
                setActivityAsyncDelegates(miBodyScope);
                miBodyScope.setProperty(BpmnProperties.TYPE.getName(), ActivityTypes.MULTI_INSTANCE_BODY);
                miBodyScope.setScope(true);
                boolean isSequential = parseBooleanAttribute(miLoopCharacteristics.attribute("isSequential"), false);
                MultiInstanceActivityBehavior behavior;
                // 自定义解析 多实例流程的 behavior
                if (isSequential) {
                    behavior = new CustomSequentialMultiInstanceActivityBehavior();
                } else {
                    behavior = new CustomParallelMultiInstanceActivityBehavior();
                }
                miBodyScope.setActivityBehavior(behavior);
                // loopCardinality
                Element loopCardinality = miLoopCharacteristics.element("loopCardinality");
                if (loopCardinality != null) {
                    String loopCardinalityText = loopCardinality.getText();
                    if (loopCardinalityText == null || "".equals(loopCardinalityText)) {
                        addError("loopCardinality must be defined for a multiInstanceLoopCharacteristics definition ", miLoopCharacteristics, id);
                    }
                    behavior.setLoopCardinalityExpression(expressionManager.createExpression(loopCardinalityText));
                }
                // completionCondition
                Element completionCondition = miLoopCharacteristics.element("completionCondition");
                if (completionCondition != null) {
                    String completionConditionText = completionCondition.getText();
                    behavior.setCompletionConditionExpression(expressionManager.createExpression(completionConditionText));
                }
                // activiti:collection
                String collection = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "collection");
                if (collection != null) {
                    if (collection.contains("{")) {
                        behavior.setCollectionExpression(expressionManager.createExpression(collection));
                    } else {
                        behavior.setCollectionVariable(collection);
                    }
                }
                // loopDataInputRef
                Element loopDataInputRef = miLoopCharacteristics.element("loopDataInputRef");
                if (loopDataInputRef != null) {
                    String loopDataInputRefText = loopDataInputRef.getText();
                    if (loopDataInputRefText != null) {
                        if (loopDataInputRefText.contains("{")) {
                            behavior.setCollectionExpression(expressionManager.createExpression(loopDataInputRefText));
                        } else {
                            behavior.setCollectionVariable(loopDataInputRefText);
                        }
                    }
                }
                // activiti:elementVariable
                String elementVariable = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "elementVariable");
                if (elementVariable != null) {
                    behavior.setCollectionElementVariable(elementVariable);
                }
                // dataInputItem
                Element inputDataItem = miLoopCharacteristics.element("inputDataItem");
                if (inputDataItem != null) {
                    String inputDataItemName = inputDataItem.attribute("name");
                    behavior.setCollectionElementVariable(inputDataItemName);
                }
                // Validation
                if (behavior.getLoopCardinalityExpression() == null && behavior.getCollectionExpression() == null && behavior.getCollectionVariable() == null) {
                    addError("Either loopCardinality or loopDataInputRef/activiti:collection must been set", miLoopCharacteristics, id);
                }
                // Validation
                if (behavior.getCollectionExpression() == null && behavior.getCollectionVariable() == null && behavior.getCollectionElementVariable() != null) {
                    addError("LoopDataInputRef/activiti:collection must be set when using inputDataItem or activiti:elementVariable", miLoopCharacteristics, id);
                }
                for (BpmnParseListener parseListener : parseListeners) {
                    parseListener.parseMultiInstanceLoopCharacteristics(activityElement, miLoopCharacteristics, miBodyScope);
                }
                return miBodyScope;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • CustomSequentialMultiInstanceActivityBehavior:通过 resolveNrOfInstances 修改获取审批人方法
    public class CustomSequentialMultiInstanceActivityBehavior extends SequentialMultiInstanceActivityBehavior {
    
        @Override
        protected int resolveNrOfInstances(ActivityExecution execution) {
            // collectionExpression 和 collectionVariable 是互斥的
            super.collectionExpression = null;
            // 这里可以自定义获取 审批人列表的 逻辑
            List<String> list = Arrays.asList("300", "301", "302");
            execution.setVariable(super.collectionVariable, list);
            return super.resolveNrOfInstances(execution);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • CustomParallelMultiInstanceActivityBehavior:与上面类似
    public class CustomParallelMultiInstanceActivityBehavior extends ParallelMultiInstanceActivityBehavior {
    
        @Override
        protected int resolveNrOfInstances(ActivityExecution execution) {
            // collectionExpression 和 collectionVariable 是互斥的
            super.collectionExpression = null;
            // 这里可以自定义获取 审批人列表的 逻辑
            List<String> list = Arrays.asList("300", "301", "302");
            execution.setVariable(super.collectionVariable, list);
            return super.resolveNrOfInstances(execution);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    查看 ACT_RU_TASK 表,可以看到,审批人已经变成了 “300”, “301”, “302”

    image-20230921234120182

    关于如何 设计 获取 审批人的策略可以参考开源仓库 ruoyi-vue-pro 的实现,不过该仓库工作流是通过 Flowable 实现的 ,但是可以参考其策略模式指定审批人的实现。

  • 相关阅读:
    数字孪生|成熟度等级
    【LeetCode】C++:新手村题单记录-重在解出问题
    【刷题】代码随想录算法训练营第二十二天|235、二叉搜索树的最近公共祖先,701、二叉搜索树中的插入操作,450、删除二叉搜索树中的节点
    【Leetcode刷题Python】生词本单词整理
    Dev C++开发环境的配置及使用
    sql函数实现模糊精确匹配
    python批量修改excel单元格内容
    Go语言核心(二)
    【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战
    Python3常用其他API速查手册(持续更新ing...)
  • 原文地址:https://blog.csdn.net/mp9105/article/details/133151953