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

2023-09-21 23:42:00

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

1.多实例案例

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

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

image-20230921213113920

bpmn核心配置为

 <bpmn:userTask id="Activity_0aal2tp" name="或签" camunda:assignee="${assignee}">
     <bpmn:incoming>Flow_10mm1vy</bpmn:incoming>
     <bpmn:outgoing>Flow_1e19xop</bpmn:outgoing>
     <bpmn:multiInstanceLoopCharacteristics camunda:collection="assigneeList" camunda:elementVariable="assignee">
         <bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances==1}</bpmn:completionCondition>
     </bpmn:multiInstanceLoopCharacteristics>
</bpmn:userTask>

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;
}

ProcessEngineFactoryBeanFactoryBean,核心方法为 getObject

public ProcessEngine getObject() throws Exception {
    if (processEngine == null) {
        initializeExpressionManager();
        initializeTransactionExternallyManaged();

        processEngine = (ProcessEngineImpl) processEngineConfiguration.buildProcessEngine();
    }

    return processEngine;
}

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

@Override
public ProcessEngine buildProcessEngine() {
    // 初始化各个组件
    init();
    processEngine = new ProcessEngineImpl(this);
    invokePostProcessEngineBuild(processEngine);
    return processEngine;
}

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

CamundaBpmAutoConfiguration#processEngineFactoryBean()
  ProcessEngineFactoryBean#getObject()
    ProcessEngineConfigurationImpl#buildProcessEngine()
      ProcessEngineConfigurationImpl#init()
        ProcessEngineConfigurationImpl#initDeployers()
          ProcessEngineConfigurationImpl#getDefaultDeployers()
            ProcessEngineConfigurationImpl#getBpmnDeployer()

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

if (bpmnParseFactory == null) {
    bpmnParseFactory = new DefaultBpmnParseFactory();
}

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

public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
    return new BpmnParse(bpmnParser);
}

BpmnParse是解析流程图的核心类

2.2 获取审批人的方法

部署一下该流程

@Test
public void testDeploy() {
    Deployment deploy = repositoryService
        .createDeployment()
        .name("多实例案例")
        .tenantId("zyx")
        .addClasspathResource("bpmn/midemo.bpmn")
        .deploy();
}

然后发起流程

@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());
}

重点关注 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;
    }
}

可以看到已经获取了 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);
    }
}

跟进到 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;
}

然后 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);
    }
}
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();
    }

}
  • 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) {
    }

}
  • CustomBpmnParseFactory
public class CustomBpmnParseFactory extends DefaultBpmnParseFactory {

    @Override
    public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
        return new CustomeBpmnParse(bpmnParser);
    }

}
  • 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;
        }
    }
}
  • 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);
    }

}
  • 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);
    }

}

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

image-20230921234120182

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

更多推荐

需求管理-架构真题(三十四)

产品配置是指一个产品在其生命周期各个阶段所产生的各种形式(机器可读或人工可读)和各种版本的()的集合。(2021)需求规格说明、设计说明、测试报告需求规格说明、设计说明、计算机程序设计说明、用户手册、计算机程序文档、计算机程序、部件及数据答案:D解析:产品配置主要就是文档、计算机程序、部件及数据的集合。需求管理的主要活

Matlab程序结构

目录顺序结构循环结构1、for循环结构分支结构1、if...end结构2、if...else...end结构3、switch...case...end结构顺序结构顺序结构是最简单的程序结构。用户在编写玩程序后,系统就将按照程序的实际位置逐一顺次执行。例1:求a、b两个数组的和>>a=[123];>>b=[456];>>

【OpenSSL】单向散列函数

什么是单向散列函数任意长度数据生成固定长度是散列快速计算消息变化散列变化单向不可逆,抗碰撞应用场景文件完整性口令加密消息认证伪随机数配合非对称加密做数字签名比特币工作量证明单向hash抗碰撞弱抗碰撞给定X和hash值的情况下,找到另外个数,hash值相同。强抗碰撞找到散列值相同的两个字符串MD5,SHA-1已经被攻破可

竞赛 基于深度学习的人脸表情识别

文章目录0前言1技术介绍1.1技术概括1.2目前表情识别实现技术2实现效果3深度学习表情识别实现过程3.1网络架构3.2数据3.3实现流程3.4部分实现代码4最后0前言🔥优质竞赛项目系列,今天要分享的是基于深度学习的人脸表情识别该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!🧿更多资料,项目分享:https:/

Learn Prompt-Prompt 高级技巧:API-Bank & AgentBench

模型评估是Agent学习过程中至关重要的一环。通过分析数据来评估Agent的能力,可以客观地衡量它在特定任务或领域中的表现。数据评估是不断迭代和改进的基础。通过反复评估和分析数据,Agent可以逐步改进自身,并不断优化其能力。数据评估还可以将Agent与其他Agent或标准进行比较,从而了解其在同一任务或领域中的相对能

Learn Prompt-GPT-4:综述

简介"GPT-4,这是OpenAI在扩大深度学习方面的最新里程碑。GPT-4是一个大型的多模态模型(接受图像和文本输入,发出文本输出),虽然在许多现实世界的场景中能力不如人类,但在各种专业和学术基准上表现出人类水平的性能。"--OpenAIGPT-4,顾名思义是GPT-3和GPT-3.5的下一代模型。相比前面的模型,G

汽车行业新闻稿怎么写?怎么写关于汽车的新闻稿?

撰写汽车行业新闻稿需要遵循一定的结构和要点,以确保内容准确、清晰,并能吸引读者的兴趣。以下是关于汽车的新闻稿的一些写作要点和建议,接下来伯乐网络传媒就来给大家分享一下:标题醒目:新闻稿的标题应该简洁明了,能够吸引读者的眼球并概括新闻的要点。可以使用一些字眼来突显重点,例如“首次亮相”、“全新发布”等。新闻价值:新闻稿应

Python Quine 介绍

一个Quine是一个产生其源代码作为输出的计算机程序。该程序不需要输入,并输出其源代码的副本。Quine很有趣,因为它们似乎违背了编程的目的,即根据输入生成输出。在某些情况下,Quine可能是有帮助的,例如当您需要生成程序源代码的副本时。运行PythonQuine创建一个Quine并不特别困难,但需要一些思考。基本思想

Python 基于PyCharm断点调试

视频版教程Python3零基础7天入门实战视频教程PyCharmDebug(断点调试)可以帮助开发者在代码运行时进行实时的调试和错误排查,提高代码开发效率和代码质量。准备一段代码defadd(num1,num2):returnnum1+num2if__name__=='__main__':fornuminrange(1

Linux Day18 TCP_UDP协议及相关知识

一、网络基础概念1.1网络网络是由若干结点和连接这些结点的链路组成,网络中的结点可以是计算机,交换机、路由器等设备。1.2互联网把多个网络连接起来就构成了互联网。目前最大的互联网就是因特网。网络设备有:交换机、路由器、集线器传输介质有:双绞线、同轴电缆、光纤,无线1.3IP地址IP地址就是给因特网上的每一个主机(或路由

【工作记录】springboot集成aop实现日志@20230918

springboot集成aop实现日志1.添加依赖<!--aop依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>2.定义注解@

热文推荐